fix(search): 업로드 파일명 매칭 제거

- 본문 검색에서 /uploads 경로와 마크다운 이미지 토큰을 제거해 파일명/해시 노이즈를 제외.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-11 16:26:21 +09:00
parent ff6526c997
commit 1b00dac21c
4 changed files with 62 additions and 6 deletions

View File

@@ -550,6 +550,20 @@ export const listTags = async () => {
const SEARCH_TAG_LIMIT = 12
const SEARCH_POST_LIMIT = 12
const SEARCH_POST_CANDIDATE_LIMIT = 48
/**
* 공개 검색에서 업로드 경로/파일명 같은 노이즈를 제거한다.
* - 마크다운 이미지 문법 `![](...)` 제거
* - 업로드 경로(`/uploads/...`) 제거
* @param {string} value - 원문
* @returns {string} 정규화된 문자열
*/
const normalizeSearchContent = (value) => String(value || '')
.replace(/!\[[^\]]*]\([^)]*\)/g, ' ')
.replace(/\/uploads\/[^\s)"]+/g, ' ')
.replace(/\s+/g, ' ')
.trim()
/**
* 공개 검색: 태그·게시물 제목·요약·본문에서 부분 일치(대소문자 무시)
@@ -568,7 +582,7 @@ export const searchPublicContent = async (rawQuery) => {
const needle = q.toLowerCase()
const posts = getSamplePosts()
.filter((post) => {
const hay = `${post.title}\n${post.excerpt || ''}\n${post.content || ''}`.toLowerCase()
const hay = normalizeSearchContent(`${post.title}\n${post.excerpt || ''}\n${post.content || ''}`).toLowerCase()
return hay.includes(needle)
})
.slice(0, SEARCH_POST_LIMIT)
@@ -601,7 +615,7 @@ export const searchPublicContent = async (rawQuery) => {
`
const postRows = await sql`
SELECT posts.slug, posts.title, posts.excerpt
SELECT posts.slug, posts.title, posts.excerpt, posts.content
FROM posts
WHERE posts.status = 'published'
AND (posts.published_at IS NULL OR posts.published_at <= now())
@@ -611,12 +625,20 @@ export const searchPublicContent = async (rawQuery) => {
OR position(lower(${q}) in lower(posts.content)) > 0
)
ORDER BY posts.published_at DESC NULLS LAST, posts.created_at DESC
LIMIT ${SEARCH_POST_LIMIT}
LIMIT ${SEARCH_POST_CANDIDATE_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: postRows.map((row) => ({
posts: posts.map((row) => ({
slug: row.slug,
title: row.title,
excerpt: row.excerpt || ''