Skip to content

feat(search): 검색어 기록(recordSearch)과 인기 검색어 시간별 스냅샷 - #251

Merged
chanwoo7 merged 1 commit into
developfrom
feat/search-record-and-keyword-rank
Aug 30, 2026
Merged

feat(search): 검색어 기록(recordSearch)과 인기 검색어 시간별 스냅샷#251
chanwoo7 merged 1 commit into
developfrom
feat/search-record-and-keyword-rank

Conversation

@chanwoo7

Copy link
Copy Markdown
Member

배경

검색 진입 화면(figma search/05) '인기 검색어 TOP10 + 순위 변동(▲/–/▼) + HH:00 기준' 대응. spec 문서가 없어 시안 + 문답으로 정책 확정(검색 화면 API 4 PR 중 1/4).

변경

  • recordSearch(keyword) mutation (OptionalJwt): 정규화(trim·공백 축약, 빈값/200자 초과 400) 후 SearchEvent 기록. 로그인 시 SearchHistory upsert(soft-delete 복원·last_used_at 갱신)와 한 트랜잭션. 검색 실행 시 1회 호출 계약.
  • popularSearchKeywords(limit=10, 최대 20): 최신 스냅샷 + 직전 스냅샷 비교 UP/DOWN/SAME/NEW. 스냅샷 없으면 빈 배열 + rankedAt: null.
  • SearchKeywordRankSnapshot 모델(마이그레이션): 매시 정각 직전 24h SearchEvent 집계 TOP20, uk(ranked_at, rank) 멱등. 불변 로그라 deleted_at 없음.
  • @nestjs/schedule 6.x 도입(12.x는 ESM 전용 → jest CJS 변환 불가). 정각 크론 + 부팅 시 1회. 실패는 로그만.
  • common/utils/search-keyword 정규화·단어 분리 순수 함수(후속 PR의 상품/매장 검색이 공유).
  • 시드: 시드 유저 소유 SearchEvent + 직전/현재 정각 스냅샷.

자체 판단(시안 외)

직전 스냅샷 = '가장 최근의 이전 스냅샷'(다운타임 허용), 동률 keyword asc, 저장 20/노출 10.

테스트

21건 추가 — 정규화 유닛, recordSearch 4경로, 스냅샷 윈도우·TOP20·멱등·soft-delete 제외, trend 4종, uk 충돌 흡수, 스케줄러, resolver 통합. yarn validate 통과, 로컬 부팅 스모크(쿼리·뮤테이션) 확인.

검색 진입 화면(figma search 05) '인기 검색어 TOP10 + 순위 변동 + HH:00 기준' 대응.
spec 문서가 없어 시안 + 문답으로 정책을 확정했다.

- recordSearch(keyword) mutation(OptionalJwt): 정규화(trim·공백 축약, 빈값/200자 초과 400)
  후 SearchEvent 기록. 로그인 시 SearchHistory upsert(soft-delete 복원·last_used_at 갱신)와
  한 트랜잭션. 검색 실행 시 1회 호출 계약(탭 전환·페이지네이션 제외).
- popularSearchKeywords(limit=10, 최대 20): 최신 스냅샷 + 직전 스냅샷 비교 UP/DOWN/SAME/NEW.
  스냅샷 없으면 빈 배열 + rankedAt null.
- SearchKeywordRankSnapshot 모델(마이그레이션): 매시 정각 직전 24h SearchEvent 집계 TOP20
  (count desc → keyword asc), uk(ranked_at, rank)로 멱등. 불변 로그라 deleted_at 없음.
- @nestjs/schedule 도입(6.x — 12.x는 ESM 전용이라 jest CJS 변환 불가): 정각 크론 +
  부팅 시 현재 정각 스냅샷 1회 생성. 실패는 로그만 남긴다.
- common/utils/search-keyword: 정규화·단어 분리 순수 함수(후속 상품/매장 검색이 공유).
- 시드: 시드 유저 소유 SearchEvent + 직전/현재 정각 스냅샷 2개(변동 4종 노출).
  resetSeedScope에 SearchEvent 정리 추가.

자체 판단(시안 외): 직전 스냅샷은 '가장 최근의 이전 스냅샷'(다운타임 허용),
동률은 keyword asc, 스냅샷 저장 20건/노출 10건.

테스트 21건: 정규화 유닛, recordSearch 로그인/비로그인/복원/400, 스냅샷 윈도우·TOP20·
멱등·빈 이벤트·soft-delete 제외, trend 4종·직전 스냅샷 탐색·limit, uk 충돌 흡수,
스케줄러 실패 삼킴, resolver 통합.
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1827a189-4a85-4b66-8091-d0d09bc5c6aa

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

🩺 NestJS Doctor — 90/100 (Excellent)

진단 279건 (error 0).

Category error warning info
architecture 0 0 9
correctness 0 132 0
performance 0 24 15
schema 0 0 86
security 0 13 0
architecture / security 상위 항목
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'IAuditLogRepository'.
  • warning security/security/no-exposed-env-vars: Direct 'process.env.NODE_ENV' access in 'AuthController'. Use ConfigService instead.
  • warning security/security/require-guards-on-endpoints: Endpoint 'start' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'callback' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'refresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'logout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogin' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerRefresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'devIssueToken' has no @UseGuards() at class or method level.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/conversation/repositories/conversation.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'ConversationRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/order/repositories/order.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'OrderRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/product/repositories/product.repository'.

오탐 포함 가능 · 기준 docs/guide/architecture-conventions.md

@github-actions

Copy link
Copy Markdown

🧹 knip — dead-code 리포트

Duplicate exports (1)
전체 리포트
Duplicate exports (1)
KEYWORD_RANK_SNAPSHOT_SIZE|MAX_POPULAR_KEYWORDS_LIMIT  src/features/search/constants/search.constants.ts

청소 후보(오탐 가능) · 기준 docs/guide/architecture-conventions.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 713f7f27aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}): Promise<KeywordCountRow[]> {
const rows = await this.prisma.searchEvent.groupBy({
by: ['keyword'],
where: { created_at: { gte: args.since, lt: args.until } },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude soft-deleted events from ranking counts

When a SearchEvent in the 24-hour window has deleted_at set, this predicate still includes it in the grouped count, so deleted searches can inflate searchCount, displace active keywords, and persist incorrect rankings in the hourly snapshot. This also contradicts the newly added soft-delete된 이벤트는 집계에서 제외한다 service test; add deleted_at: null to the aggregation filter.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

false positive: groupBy는 soft-delete Prisma extension의 루트 READ 대상(READ_ACTIONSgroupBy 포함)이라 deleted_at: null이 자동 주입됨. 레포 규칙상 루트 READ에는 중복 명시하지 않음(CLAUDE.md). 같은 PR의 soft-delete된 이벤트는 집계에서 제외한다 실DB 테스트가 이를 검증하며 통과함. 기존 aggregateProductRecentOrderCounts(orderItem.groupBy)도 동일 전제.

@codecov

codecov Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 97.83% 5185/5300
🟢 Branches 94.31% 2039/2162
🟢 Functions 96.37% 1010/1048
🟢 Lines 98.37% 4711/4789

Test suite run success

1717 tests passing in 199 suites.

Report generated by 🧪jest coverage report action from 713f7f2

@chanwoo7
chanwoo7 merged commit b903900 into develop Aug 30, 2026
12 checks passed
@chanwoo7
chanwoo7 deleted the feat/search-record-and-keyword-rank branch August 30, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant