Skip to content

Commit

Permalink
Feat: News 통합 검색 API 구현 (#124)
Browse files Browse the repository at this point in the history
* Feat: Add dto for total search of news.

* Feat: Add repository for total search.

* Feat: Add service, controller for news total search.

* Fix: Fix conflict issues.
  • Loading branch information
huGgW authored Sep 18, 2023
1 parent cc141a8 commit bb5e7ac
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import com.wafflestudio.csereal.core.news.service.NewsService
import com.wafflestudio.csereal.core.user.database.Role
import com.wafflestudio.csereal.core.user.database.UserRepository
import jakarta.validation.Valid
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Positive
import org.hibernate.validator.constraints.Length
import org.springframework.data.domain.PageRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
Expand Down Expand Up @@ -41,6 +44,15 @@ class NewsController(
return ResponseEntity.ok(newsService.searchNews(tag, keyword, pageRequest, usePageBtn, isStaff))
}

@GetMapping("/totalSearch")
fun searchTotalNews(
@RequestParam(required = true) @Length(min = 1) @NotBlank keyword: String,
@RequestParam(required = true) @Positive number: Int,
@RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int,
) = ResponseEntity.ok(
newsService.searchTotalNews(keyword, number, stringLength)
)

@GetMapping("/{newsId}")
fun readNews(
@PathVariable newsId: Long
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import com.wafflestudio.csereal.common.utils.FixedPageRequest
import com.wafflestudio.csereal.common.utils.cleanTextFromHtml
import com.wafflestudio.csereal.core.news.database.QNewsEntity.newsEntity
import com.wafflestudio.csereal.core.news.database.QNewsTagEntity.newsTagEntity
import com.wafflestudio.csereal.core.news.database.QTagInNewsEntity.tagInNewsEntity
import com.wafflestudio.csereal.core.news.dto.NewsSearchDto
import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse
import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchDto
import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchElement
import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity
import com.wafflestudio.csereal.core.resource.mainImage.database.QMainImageEntity.mainImageEntity
import com.wafflestudio.csereal.core.notice.database.QNoticeEntity
import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService
import org.springframework.data.domain.Pageable
Expand All @@ -24,13 +29,13 @@ interface NewsRepository : JpaRepository<NewsEntity, Long>, CustomNewsRepository
}

interface CustomNewsRepository {
fun searchNews(
tag: List<String>?,
keyword: String?,
pageable: Pageable,
usePageBtn: Boolean,
isStaff: Boolean
): NewsSearchResponse
fun searchNews(tag: List<String>?, keyword: String?, pageable: Pageable, usePageBtn: Boolean, isStaff: Boolean): NewsSearchResponse
fun searchTotalNews(
keyword: String,
number: Int,
amount: Int,
imageUrlCreator: (MainImageEntity?) -> String?,
): NewsTotalSearchDto
}

@Component
Expand Down Expand Up @@ -111,4 +116,64 @@ class NewsRepositoryImpl(
}
return NewsSearchResponse(total, newsSearchDtoList)
}

override fun searchTotalNews(
keyword: String,
number: Int,
amount: Int,
imageUrlCreator: (MainImageEntity?) -> String?,
): NewsTotalSearchDto {
val doubleTemplate = commonRepository.searchFullDoubleTextTemplate(
keyword,
newsEntity.title,
newsEntity.plainTextDescription,
)

val searchResult = queryFactory.select(
newsEntity.id,
newsEntity.title,
newsEntity.date,
newsEntity.plainTextDescription,
mainImageEntity,
).from(newsEntity)
.leftJoin(mainImageEntity)
.where(doubleTemplate.gt(0.0))
.limit(number.toLong())
.fetch()

val searchResultTags = queryFactory.select(
newsTagEntity.news.id,
newsTagEntity.tag.name,
).from(newsTagEntity)
.rightJoin(newsEntity)
.leftJoin(tagInNewsEntity)
.where(newsTagEntity.news.id.`in`(searchResult.map { it[newsEntity.id] }))
.distinct()
.fetch()

val total = queryFactory.select(newsEntity.countDistinct())
.from(newsEntity)
.where(doubleTemplate.gt(0.0))
.fetchOne()!!

return NewsTotalSearchDto(
total.toInt(),
searchResult.map {
NewsTotalSearchElement(
id = it[newsEntity.id]!!,
title = it[newsEntity.title]!!,
date = it[newsEntity.date],
tags = searchResultTags.filter {
tag -> tag[newsTagEntity.news.id] == it[newsEntity.id]
}.map {
tag -> tag[newsTagEntity.tag.name]!!.krName
},
imageUrl = imageUrlCreator(it[mainImageEntity]),
description = it[newsEntity.plainTextDescription]!!,
keyword = keyword,
amount = amount,
)
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.wafflestudio.csereal.core.news.dto

data class NewsTotalSearchDto (
val total: Int,
val results: List<NewsTotalSearchElement>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.wafflestudio.csereal.core.news.dto

import com.wafflestudio.csereal.common.utils.substringAroundKeyword
import java.time.LocalDateTime

data class NewsTotalSearchElement private constructor(
val id: Long,
val title: String,
val date: LocalDateTime?,
val tags: List<String>,
val imageUrl: String?,
) {
lateinit var partialDescription: String
var boldStartIndex: Int = 0
var boldEndIndex: Int = 0

constructor(
id: Long,
title: String,
date: LocalDateTime?,
tags: List<String>,
imageUrl: String?,
description: String,
keyword: String,
amount: Int,
) : this(id, title, date, tags, imageUrl) {
val (startIdx, substring) = substringAroundKeyword(keyword, description, amount)
partialDescription = substring
boldStartIndex = startIdx ?: 0
boldEndIndex = startIdx?.plus(keyword.length) ?: 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.CserealException
import com.wafflestudio.csereal.core.news.database.*
import com.wafflestudio.csereal.core.news.dto.NewsDto
import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse
import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchDto
import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService
import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService
import org.springframework.data.domain.Pageable
Expand Down Expand Up @@ -32,6 +33,7 @@ interface NewsService {

fun deleteNews(newsId: Long)
fun enrollTag(tagName: String)
fun searchTotalNews(keyword: String, number: Int, amount: Int): NewsTotalSearchDto
}

@Service
Expand All @@ -53,6 +55,18 @@ class NewsServiceImpl(
return newsRepository.searchNews(tag, keyword, pageable, usePageBtn, isStaff)
}

@Transactional(readOnly = true)
override fun searchTotalNews(
keyword: String,
number: Int,
amount: Int,
) = newsRepository.searchTotalNews(
keyword,
number,
amount,
mainImageService::createImageURL,
)

@Transactional(readOnly = true)
override fun readNews(newsId: Long): NewsDto {
val news: NewsEntity = newsRepository.findByIdOrNull(newsId)
Expand Down

0 comments on commit bb5e7ac

Please sign in to comment.