공개 검색 인덱스 추가

This commit is contained in:
2026-07-03 15:43:27 +09:00
parent 33c92a6f72
commit 58fbae5a03
10 changed files with 148 additions and 42 deletions

View File

@@ -1721,7 +1721,6 @@ export const listAdminTags = async (options = {}) => listTags(options)
const SEARCH_TAG_LIMIT = 12
const SEARCH_POST_LIMIT = 12
const SEARCH_PAGE_LIMIT = 8
const SEARCH_POST_CANDIDATE_LIMIT = 48
/**
* 공개 검색어를 비교 가능한 문자열로 정리한다.
@@ -1756,11 +1755,15 @@ export const searchPublicContent = async (rawQuery) => {
if (!q) {
return { tags: [], posts: [], pages: [] }
}
const searchTextQuery = normalizeSearchContent(q).toLowerCase()
if (!searchTextQuery) {
return { tags: [], posts: [], pages: [] }
}
const sql = getPostgresClient()
if (!sql) {
const needle = q.toLowerCase()
const needle = searchTextQuery
const posts = getSamplePosts()
.filter((post) => {
const hay = normalizeSearchContent(`${post.title}\n${post.excerpt || ''}\n${post.content || ''}`).toLowerCase()
@@ -1796,58 +1799,67 @@ export const searchPublicContent = async (rawQuery) => {
return { tags, posts, pages }
}
const searchPattern = `%${searchTextQuery}%`
const searchPrefixPattern = `${searchTextQuery}%`
const tagRows = await sql`
SELECT name, slug
FROM tags
WHERE
position(lower(${q}) in lower(name)) > 0
OR position(lower(${q}) in lower(slug)) > 0
ORDER BY sort_order ASC, name ASC
lower(name) LIKE ${searchPattern}
OR lower(slug) LIKE ${searchPattern}
ORDER BY
CASE
WHEN lower(name) = ${searchTextQuery} OR lower(slug) = ${searchTextQuery} THEN 0
WHEN lower(name) LIKE ${searchPrefixPattern} OR lower(slug) LIKE ${searchPrefixPattern} THEN 1
ELSE 2
END,
sort_order ASC,
name ASC
LIMIT ${SEARCH_TAG_LIMIT}
`
const postRows = await sql`
SELECT posts.slug, posts.title, posts.excerpt, posts.content
SELECT posts.slug, posts.title, posts.excerpt
FROM posts
WHERE posts.status = 'published'
AND (posts.published_at IS NULL OR posts.published_at <= now())
AND (
position(lower(${q}) in lower(posts.title)) > 0
OR position(lower(${q}) in lower(coalesce(posts.excerpt, ''))) > 0
OR position(lower(${q}) in lower(posts.content)) > 0
)
ORDER BY posts.published_at DESC NULLS LAST, posts.created_at DESC
LIMIT ${SEARCH_POST_CANDIDATE_LIMIT}
AND posts.search_text LIKE ${searchPattern}
ORDER BY
CASE
WHEN lower(posts.title) = ${searchTextQuery} THEN 0
WHEN lower(posts.title) LIKE ${searchPrefixPattern} THEN 1
WHEN lower(coalesce(posts.excerpt, '')) LIKE ${searchPrefixPattern} THEN 2
ELSE 3
END,
posts.published_at DESC NULLS LAST,
posts.created_at DESC
LIMIT ${SEARCH_POST_LIMIT}
`
const pageRows = await sql`
SELECT slug, title, content, listing_section, listing_title, listing_description
SELECT slug, title, listing_section, listing_title, listing_description
FROM pages
WHERE status = 'published'
AND is_listed = true
AND (
position(lower(${q}) in lower(title)) > 0
OR position(lower(${q}) in lower(coalesce(listing_section, ''))) > 0
OR position(lower(${q}) in lower(coalesce(listing_title, ''))) > 0
OR position(lower(${q}) in lower(coalesce(listing_description, ''))) > 0
OR position(lower(${q}) in lower(content)) > 0
)
ORDER BY is_featured DESC, listing_section ASC, listing_sort_order ASC, updated_at DESC
AND search_text LIKE ${searchPattern}
ORDER BY
CASE
WHEN lower(coalesce(listing_title, title)) = ${searchTextQuery} THEN 0
WHEN lower(coalesce(listing_title, title)) LIKE ${searchPrefixPattern} THEN 1
WHEN lower(coalesce(listing_description, '')) LIKE ${searchPrefixPattern} THEN 2
ELSE 3
END,
is_featured DESC,
listing_section ASC,
listing_sort_order ASC,
updated_at DESC
LIMIT ${SEARCH_PAGE_LIMIT}
`
const needle = q.toLowerCase()
const posts = postRows
.filter((row) => {
const hay = normalizeSearchContent(`${row.title}\n${row.excerpt || ''}\n${row.content || ''}`).toLowerCase()
return hay.includes(needle)
})
.slice(0, SEARCH_POST_LIMIT)
return {
tags: tagRows.map((row) => ({ name: row.name, slug: row.slug })),
posts: posts.map((row) => ({
posts: postRows.map((row) => ({
slug: row.slug,
title: row.title,
excerpt: row.excerpt || ''