Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: News 통합 검색 API 구현 #124

Merged
merged 7 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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