[Feat] : 검색 단어 단위 AND 매칭 및 부분 일치 NULL 누락 버그 수정 (#254)#255
Conversation
- 검색어를 공백 기준 토큰으로 분리해 모든 단어가 포함돼야 매칭(순서 무관)
- 정확 티어는 tokens.join('%')로 띄어쓰기 차이 무시 완전 일치
- title_ko/artist_ko가 null일 때 NOT ILIKE가 NULL로 평가돼 행이 누락되던 버그를 IS NULL OR로 수정
- useSearchSong: 한글 한정 공백 제거 로직 삭제(공백 보존, 토큰화는 API 담당)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
/describe |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
/review |
|
/improve |
Code Review by Qodo
Context used✅ Tickets:
🎫 검색 기능 개편 — 중간 띄어쓰기 처리 및 쿼리 검증 1. Type 미검증 사용
|
PR Summary by Qodo검색: 토큰 AND 매칭 도입 및 NULL-safe 부분 검색 버그 수정 WalkthroughsDescription• 검색어를 공백 토큰으로 분리해 모든 단어 포함(AND) 시에만 부분 검색되도록 개선 • 정확 검색은 토큰을 %로 연결해 띄어쓰기 차이를 무시한 구(phrase) 매칭으로 상향 • nullable 컬럼의 NOT ILIKE가 NULL로 누락되던 부분 검색 버그를 NULL-safe 조건으로 수정 Diagramgraph TD
A["Search UI (useSearchSong)"] --> B["/api/search (route.ts)"] --> C["executeSearchQueries()"]
C --> D["applyExactFilter()"] --> F[("Supabase/PostgREST")] --> G[("songs table")]
C --> E["applyPartialFilter()"] --> F
High-Level AssessmentThe following are alternative approaches to this PR: 1. Postgres Full-Text Search(tsvector)로 전환
2. Supabase RPC(Stored Procedure)로 검색 로직 캡슐화
Recommendation: 현 단계에서는 PR의 접근(API에서 토큰화 + PostgREST .or 체이닝 AND + NULL-safe 제외)이 변경 범위 대비 효과가 크고, 클라이언트 공백 처리도 API로 일원화되어 합리적이다. 다만 데이터 규모가 커지거나 랭킹/검색 품질 요구가 증가하면 FTS(tsvector) 또는 RPC로 검색 규칙을 DB 레이어로 이동하는 방안을 고려하는 것이 장기적으로 안정적이다. File ChangesEnhancement (1)
Bug fix (1)
|
| function getSearchColumns(type: string): string[] { | ||
| return SEARCH_COLUMNS[type] ?? [type]; | ||
| } |
There was a problem hiding this comment.
1. Type 미검증 사용 🐞 Bug ⛨ Security
GET /api/search의 type 파라미터를 검증하지 않고 getSearchColumns()가 미지원 type을 컬럼명으로 그대로 사용합니다. 임의의 type으로 잘못된 컬럼/정렬이 구성되어 검색이 500으로 실패하거나 의도치 않은 필터링/정렬이 발생할 수 있습니다.
Agent Prompt
## Issue description
`type`이 URL 파라미터에서 그대로 들어오며, 미지원 값은 `getSearchColumns()`에서 `[type]`로 fallback되어 PostgREST 필터/정렬 컬럼으로 사용됩니다. 이로 인해 잘못된 컬럼명으로 쿼리가 깨지거나(500), 의도치 않은 컬럼 기준의 필터/정렬이 가능합니다.
## Issue Context
- 클라이언트 타입(`SearchType`)은 제한돼도, API Route는 외부에서 직접 호출될 수 있습니다.
- 현재 `order`도 `type`에 의존하여 임의 컬럼 정렬로 이어질 수 있습니다.
## Fix Focus Areas
- apps/web/src/app/api/search/route.ts[32-34]
- apps/web/src/app/api/search/route.ts[199-206]
### Suggested fix
1) GET handler에서 `type`을 `all|title|artist|number`로 whitelist 검증하고, 미일치 시 400 반환.
2) `getSearchColumns()`의 fallback (`?? [type]`) 제거(또는 unreachable 처리)하여 미지원 type이 컬럼으로 사용되지 않게 함.
3) `order`도 `{ all: 'title', title: 'title', artist: 'artist', number: 'num_tj' }` 같은 고정 매핑으로 계산.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Qodo 리뷰 대응: - 토큰화 전 PostgREST .or() 예약문자(, ( ) ") 공백 치환 → 콤마/괄호 포함 검색어로 필터가 분리/파손되던 문제 해결 - GET 핸들러에서 q를 trim 후 빈 값은 400 처리, 유효 토큰 0개면 빈 결과 반환 → 공백/예약문자-only 입력으로 빈 패턴 필터가 생성되던 문제 방어 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📌 PR 제목
[Feat] : 검색 단어 단위 AND 매칭 및 부분 일치 NULL 누락 버그 수정
📌 변경 사항
api/search/route.ts필터 재작성tokens.join('%')로 띄어쓰기 차이를 무시한 완전 일치 (예:사건의 지평선→사건의%지평선).or()체이닝으로 AND 결합 (바톤 로드검색 시오버로드·로드넘버원같은 단어 하나만 걸친 노이즈 제외)title_ko/artist_ko가 null일 때NOT ILIKE가NULL로 평가되어 행이 통째로 누락되던 문제를,col IS NULL을 OR로 묶어 NULL-safe하게 처리. (이전엔 title_ko가 채워진 J-pop만 부분 검색이 동작)useSearchSong.ts: 한글 한정 공백 제거 로직 삭제 → 공백을 보존해 API로 전달 (토큰화/공백 처리는 API가 담당)buildColumnOrGroup헬퍼로 추출 (DRY)💬 추가 참고 사항
바톤 로드→1건,사건의 지평선→1건, 단일 토큰·number·페이지네이션·정확-우선 랭킹 회귀 없음)