Skip to content

[FEAT] 파트너 저장/삭제 및 검색 제외 처리#23

Merged
Takch02 merged 6 commits into
mainfrom
feat/save-partner
May 10, 2026
Merged

[FEAT] 파트너 저장/삭제 및 검색 제외 처리#23
Takch02 merged 6 commits into
mainfrom
feat/save-partner

Conversation

@Takch02

@Takch02 Takch02 commented May 10, 2026

Copy link
Copy Markdown
Contributor

Resolves #18

변경 사항

My Business

  • POST /my-business/partners — 파트너 저장 (seller/buyer 구분)
  • DELETE /my-business/partners/:partnerId — 저장된 파트너 삭제
  • 중복 저장 시 409, 존재하지 않는 파트너 저장 시 404 응답

Partners 검색

  • 로그인 사용자의 경우 이미 저장한 파트너를 검색 결과에서 자동 제외
  • 비로그인 사용자는 기존과 동일하게 전체 결과 반환

기타

  • OptionalCurrentUser 데코레이터 추가 (인증 선택적 엔드포인트용)
  • my-business 도메인 커스텀 예외 클래스 분리 (PartnerAlreadySavedException, PartnerNotFoundException, SavedPartnerNotFoundException)

테스트

  • my-business.service.spec.ts — savePartner / removePartner 케이스 추가
  • partners.service.spec.ts — savedPartners $nin 필터 적용 케이스 추가 (로그인/비로그인/빈 목록)

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 파트너를 저장하고 저장 목록에서 제거할 수 있는 기능이 추가되었습니다.
    • 파트너 검색 시 이미 저장된 파트너가 자동으로 제외됩니다.
    • 인증되지 않은 사용자도 파트너를 검색할 수 있도록 개선되었습니다.
  • 테스트

    • 파트너 저장/제거 및 검색 기능에 대한 테스트가 추가되었습니다.

Review Change Stack

Takch02 and others added 3 commits May 10, 2026 13:54
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Takch02 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 20 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32001e48-5618-46b9-93f1-92bde77684fe

📥 Commits

Reviewing files that changed from the base of the PR and between 1ae01af and c416e11.

📒 Files selected for processing (4)
  • src/modules/my-business/my-business.service.spec.ts
  • src/modules/my-business/my-business.service.ts
  • src/modules/partners/partners.controller.spec.ts
  • src/modules/partners/partners.controller.ts
📝 Walkthrough

개요

사용자가 선호하는 파트너를 저장하고 제거할 수 있는 기능을 구현합니다. 저장된 파트너는 향후 검색 결과에서 자동으로 제외되며, 선택적 사용자 인증을 위한 새로운 decorator를 추가합니다.

주요 변경 사항

파트너 저장/제거 기능

계층 / 파일 요약
공통 인증 기반
src/common/decorators/current-user.decorator.ts
OptionalCurrentUser decorator를 추가하여 선택적 사용자 인증을 지원. 세션 값이 없으면 null을 반환.
예외 클래스
src/common/exceptions/my-business.exceptions.ts, src/common/exceptions/index.ts
PartnerAlreadySavedException, PartnerNotFoundException, SavedPartnerNotFoundException 추가.
데이터 계약
src/modules/my-business/dto/save-partner.dto.ts, src/modules/partners/partners.service.ts
SavePartnerDtoSearchOptions 인터페이스에 userId 필드 추가.
My-Business 서비스
src/modules/my-business/my-business.service.ts
savePartner()removePartner() 메서드 구현. 파트너 존재 여부 검증 후 user.savedPartners 배열 관리.
My-Business 컨트롤러
src/modules/my-business/my-business.controller.ts
POST /my-business/partnersDELETE /my-business/partners/:partnerId 엔드포인트 추가.
파트너 검색 통합
src/modules/partners/partners.service.ts, src/modules/partners/partners.controller.ts
로그인 사용자의 savedPartners를 조회하여 모든 검색 경로(텍스트 검색, 벡터 검색, 필터링, 전체 조회)에서 제외 필터 $nin 적용.
테스트
src/modules/my-business/my-business.service.spec.ts, src/modules/partners/partners.service.spec.ts
저장/제거 성공, 중복 방지, 파트너 미존재, 필터링 검증 테스트.

검토 포인트

🔍 주요 로직 검토 사항

  1. 파트너 저장 시 중복 체크 (my-business.service.ts, 라인 73-95)

    • exists() 호출로 파트너 존재 여부 확인 ✓
    • partnerType에 따른 seller/buyer 모델 선택 분기 ✓
    • 그러나 중복 저장 체크는 $addToSet 대신 push 사용하는데, updateOne 호출 전 중복 확인 로직이 있으므로 문제없음 ✓
  2. 파트너 제거 로직 (my-business.service.ts, 라인 97-110)

    • modifiedCount 체크로 실제 삭제 여부 확인 ✓
    • 잘못된 ObjectId 형식에 대한 catch 처리 고려 필요 ⚠️
  3. 검색 필터링 로직 (partners.service.ts)

    • 사용자 savedPartners 조회 후 $nin 필터 적용 ✓
    • 모든 검색 경로(text, vector, filter, show-all)에 일관되게 적용 ✓
    • userId 없을 때는 필터링 스킵 ✓
    • 다만 userModel.findById().lean() 실패 시 처리 방안 확인 필요 ⚠️
  4. 데이터 검증

    • SavePartnerDto@IsMongoId()@IsIn() 데코레이터로 입력값 검증 ✓
    • 컨트롤러에서 @Body() 파이프를 통한 자동 검증 ✓

🔐 보안 체크

  • @CurrentUser() 가드로 인증 사용자만 저장/제거 가능 ✓
  • @OptionalCurrentUser()로 미인증 사용자도 검색 가능하되 필터링 미적용 ✓

⚠️ 잠재적 개선 사항

  1. 에러 핸들링: removePartner에서 잘못된 ObjectId 형식에 대한 명시적 처리 추가 권장
  2. 쿼리 성능: PartnersService.search에서 User 모델 조회 시 projection 사용으로 필드 제한 적용됨 ✓
  3. 트랜잭션: 파트너 저장 후 검증 사이의 레이스 컨디션 가능성은 낮음 (대부분의 경우 문제없음)

시퀀스 다이어그램

sequenceDiagram
  participant User as 사용자
  participant BrowserClient as 클라이언트
  participant MyBusiness as MyBusinessAPI
  participant PartnerAPI as PartnersAPI
  participant UserDB as UserDB
  participant PartnerDB as PartnerDB

  User->>BrowserClient: 파트너 저장 요청
  BrowserClient->>MyBusiness: POST /my-business/partners<br/>{partnerId, partnerType}
  MyBusiness->>PartnerDB: 파트너 존재 여부 확인
  PartnerDB-->>MyBusiness: 파트너 확인됨
  MyBusiness->>UserDB: user.savedPartners에 추가
  UserDB-->>MyBusiness: 업데이트 완료
  MyBusiness-->>BrowserClient: 성공 응답
  BrowserClient-->>User: 저장 완료

  User->>BrowserClient: 파트너 검색
  BrowserClient->>PartnerAPI: GET /partners/search?query=...
  PartnerAPI->>UserDB: 사용자의 savedPartners 조회
  UserDB-->>PartnerAPI: savedPartners 반환
  PartnerAPI->>PartnerDB: 검색 ($nin 필터로 제외)
  PartnerDB-->>PartnerAPI: 필터링된 파트너 목록
  PartnerAPI-->>BrowserClient: 검색 결과
  BrowserClient-->>User: 저장되지 않은 파트너만 표시
Loading

예상 코드 리뷰 난이도

🎯 3 (중간) | ⏱️ ~25분

난이도 분석:

  • 새로운 기능의 일관성 있는 구현 (저장, 제거, 검색 통합)
  • 다양한 파일과 계층에 걸친 변경 (컨트롤러, 서비스, 데이터베이스 쿼리)
  • MongoDB 필터링 로직 검증 필요 (여러 검색 경로에 $nin 적용)
  • 테스트 커버리지 완전함

관련 PR

  • K-Statra/backend#19: My Business 도메인 및 savedPartners 필드 초기 추가로, 본 PR이 구축하는 기반 제공
  • K-Statra/backend#10: PartnersController/PartnersService 수정 (본 PR에서 선택적 사용자 인증 및 검색 제외 로직 추가)

축하 시

🎯 저장했나 했더니, 검색에서 쏙 사라지네!
파트너 목록 스무스하게 관리,
사용자 선택은 존중받고,
저장된 것들은 조용히 숨어,
진정한 발견의 기쁨이 여기! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main features: partner save/delete and search exclusion handling for authenticated users.
Linked Issues check ✅ Passed All coding requirements from issue #18 are met: partner save API, persistence to Users.savedPartners, and exclusion from search results for authenticated users.
Out of Scope Changes check ✅ Passed All changes are within scope of the linked issue #18, including supporting infrastructure like OptionalCurrentUser decorator and custom exceptions for the my-business domain.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/save-partner

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 and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/modules/partners/partners.service.spec.ts (1)

259-272: ⚡ Quick win

$nin 배열의 첫 번째 요소만 검증하고 있습니다.

Line 271에서 filterArg._id.$nin[0]만 확인하는데, 현재 테스트는 savedPartners가 1개일 때만 동작합니다. 향후 여러 파트너가 저장된 경우를 대비해 배열 전체 또는 길이도 검증하는 것이 좋습니다.

♻️ 제안: 배열 전체 검증 추가
     const [filterArg] = sellerModel.find.mock.calls[0];
-    expect(filterArg._id.$nin[0].toString()).toBe(savedId.toString());
+    expect(filterArg._id.$nin).toHaveLength(1);
+    expect(filterArg._id.$nin[0].toString()).toBe(savedId.toString());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/partners/partners.service.spec.ts` around lines 259 - 272, The
test currently only asserts the first element of the $nin array
(filterArg._id.$nin[0]) against savedId; update the assertion to validate the
whole array returned to handle multiple saved partners: when mocking
userModel.findById to return savedPartners (e.g., savedPartners array of
partnerId values), assert filterArg._id.$nin has the expected length and that
every element (or the set) matches the saved partnerIds (use the same
savedPartners data you mock), referencing the variables savedId/savedPartners
and the sellerModel.find call to locate the assertion.
src/modules/partners/partners.service.ts (2)

345-346: 벡터 검색에서 $nin 필터 위치로 인한 결과 수 감소 가능성을 고려해야 합니다.

MongoDB Atlas의 $vectorSearch는 먼저 100개의 후보를 찾은 후, $match 단계에서 $nin 필터가 적용됩니다. 사용자의 savedPartners에 많은 ID가 포함된 경우, 최종 결과가 요청한 limit보다 훨씬 적을 수 있습니다.

현재 구조는 Atlas vector search의 표준 패턴이지만, 사용자가 많은 파트너를 저장했을 때 결과 부족 현상이 발생할 수 있음을 인지하고 모니터링하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/partners/partners.service.ts` around lines 345 - 346, The current
use of matchStage._id = { $nin: excludeObjectIds } after $vectorSearch can
shrink results because Atlas finds ~100 candidates before $match; to fix, detect
when excludeObjectIds is large and either (A) increase the vector search
candidate pool/limit parameter in the $vectorSearch stage so more candidates are
returned before $match, or (B) remove the $nin from matchStage and perform
exclusion client-side by running $vectorSearch (e.g., using the existing vector
search stage) then filtering results by excludeObjectIds in the service code (in
the function surrounding matchStage and the $vectorSearch invocation) and
optionally repeat with a larger candidate limit until the requested limit is
fulfilled; reference symbols: matchStage, excludeObjectIds, $vectorSearch.

196-205: 대량의 savedPartners가 있을 때 쿼리 성능 저하 가능성을 모니터링해야 합니다.

MongoDB의 $nin 연산자는 배열 크기가 커질수록 성능이 저하될 수 있습니다. 사용자가 수백 개 이상의 파트너를 저장한 경우, 인덱스 활용이 제한되고 전체 컬렉션 스캔이 발생할 수 있습니다.

현재 구현은 초기 버전으로 적절하지만, 향후 다음을 고려할 수 있습니다:

  • savedPartners 개수에 실용적인 제한 설정 (예: 최대 1000개)
  • 대량 저장 시 다른 필터링 전략 사용 (예: 별도 쿼리로 결과 후처리)
  • 성능 메트릭 수집 및 모니터링
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/partners/partners.service.ts` around lines 196 - 205, The current
exclusion logic builds excludeObjectIds from user.savedPartners which can lead
to a large $nin array and degrade query performance; modify the block in
partners.service.ts (referencing opts.userId, this.userModel, savedPartners and
partnerId used to populate excludeObjectIds) to impose a practical cap (e.g.
only take first N savedPartners like 1000) or switch to a safer strategy (e.g.
fetch results without $nin and filter out saved partnerIds in-memory when
savedPartners is huge), and add a metric/log when the savedPartners length
exceeds the cap so we can monitor occurrences and adjust later.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/modules/my-business/my-business.service.ts`:
- Around line 80-94: Replace the separate exists-check + push with an atomic
conditional update: first verify the partner exists in the chosen model (use
sellerModel or buyerModel as in the diff and throw PartnerNotFoundException if
not), then call this.userModel.updateOne({ _id: userId,
"savedPartners.partnerId": { $ne: partnerObjectId } }, { $push: { savedPartners:
{ partnerId: partnerObjectId, partnerType } } }) (or use $addToSet) so the
check-and-insert is atomic, and inspect the returned result
(matchedCount/modifiedCount) to decide outcome—if matchedCount is 0 throw
PartnerNotFoundException or user-missing, if modifiedCount is 0 throw
PartnerAlreadySavedException, otherwise return the success message; reference
userModel, savedPartners, partnerObjectId, partnerType, sellerModel, buyerModel,
PartnerNotFoundException, PartnerAlreadySavedException.
- Around line 73-79: Validate partnerId before constructing a new Types.ObjectId
in savePartner: perform the same pre-check used in removePartner to ensure
partnerId is a valid ObjectId string and return/throw a controlled error if
invalid instead of calling new Types.ObjectId(partnerId) blindly; after
performing the updateOne call in savePartner (the block that currently writes
partner info, lines ~90-94), inspect the result.modifiedCount (or matchedCount
as appropriate) and return/fail when no document was modified so the caller does
not receive a false success—mirror the error/response behavior used in
removePartner for both validation and update result handling.

In `@src/modules/partners/partners.controller.ts`:
- Around line 10-13: The absolute import of OptionalCurrentUser and SessionUser
("src/common/decorators/current-user.decorator") in partners.controller.ts
breaks Jest resolution; replace that absolute import with a correct relative
import that points to the same current-user.decorator module so the imports for
OptionalCurrentUser and SessionUser resolve during tests and CI.

---

Nitpick comments:
In `@src/modules/partners/partners.service.spec.ts`:
- Around line 259-272: The test currently only asserts the first element of the
$nin array (filterArg._id.$nin[0]) against savedId; update the assertion to
validate the whole array returned to handle multiple saved partners: when
mocking userModel.findById to return savedPartners (e.g., savedPartners array of
partnerId values), assert filterArg._id.$nin has the expected length and that
every element (or the set) matches the saved partnerIds (use the same
savedPartners data you mock), referencing the variables savedId/savedPartners
and the sellerModel.find call to locate the assertion.

In `@src/modules/partners/partners.service.ts`:
- Around line 345-346: The current use of matchStage._id = { $nin:
excludeObjectIds } after $vectorSearch can shrink results because Atlas finds
~100 candidates before $match; to fix, detect when excludeObjectIds is large and
either (A) increase the vector search candidate pool/limit parameter in the
$vectorSearch stage so more candidates are returned before $match, or (B) remove
the $nin from matchStage and perform exclusion client-side by running
$vectorSearch (e.g., using the existing vector search stage) then filtering
results by excludeObjectIds in the service code (in the function surrounding
matchStage and the $vectorSearch invocation) and optionally repeat with a larger
candidate limit until the requested limit is fulfilled; reference symbols:
matchStage, excludeObjectIds, $vectorSearch.
- Around line 196-205: The current exclusion logic builds excludeObjectIds from
user.savedPartners which can lead to a large $nin array and degrade query
performance; modify the block in partners.service.ts (referencing opts.userId,
this.userModel, savedPartners and partnerId used to populate excludeObjectIds)
to impose a practical cap (e.g. only take first N savedPartners like 1000) or
switch to a safer strategy (e.g. fetch results without $nin and filter out saved
partnerIds in-memory when savedPartners is huge), and add a metric/log when the
savedPartners length exceeds the cap so we can monitor occurrences and adjust
later.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a0da567f-7c31-46a1-bf9c-0361a457373c

📥 Commits

Reviewing files that changed from the base of the PR and between ab9e5c6 and 1ae01af.

📒 Files selected for processing (10)
  • src/common/decorators/current-user.decorator.ts
  • src/common/exceptions/index.ts
  • src/common/exceptions/my-business.exceptions.ts
  • src/modules/my-business/dto/save-partner.dto.ts
  • src/modules/my-business/my-business.controller.ts
  • src/modules/my-business/my-business.service.spec.ts
  • src/modules/my-business/my-business.service.ts
  • src/modules/partners/partners.controller.ts
  • src/modules/partners/partners.service.spec.ts
  • src/modules/partners/partners.service.ts

Comment thread src/modules/my-business/my-business.service.ts
Comment thread src/modules/my-business/my-business.service.ts Outdated
Comment thread src/modules/partners/partners.controller.ts Outdated
@Takch02 Takch02 merged commit 01ceb0d into main May 10, 2026
3 checks passed
@Takch02 Takch02 deleted the feat/save-partner branch May 11, 2026 01:10
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.

[FEAT] partners.serarch 파트너 추가 기능

1 participant