Skip to content

Feat/#141 예산 수정 - 네이버 광고 예산 수정 API 구현#142

Open
kingmingyu wants to merge 11 commits into
developfrom
feat/#141
Open

Feat/#141 예산 수정 - 네이버 광고 예산 수정 API 구현#142
kingmingyu wants to merge 11 commits into
developfrom
feat/#141

Conversation

@kingmingyu
Copy link
Copy Markdown
Collaborator

@kingmingyu kingmingyu commented May 29, 2026

📌 관련 이슈

🚀 개요

이번 PR에서 변경된 핵심 내용을 요약해주세요.

네이버 광고 예산 수정 API를 이용하여 캠페인 or 광고 그룹에 할당된 예산을 수정합니다.

📄 작업 내용

구체적인 작업 내용을 설명해주세요.

  • NaverClient 네이버 광고 예산 수정 엔드포인트 추가
  • 요청 DTO 추가 및 입력값 검증(예산은 10의 배수)
  • 서비스 로직 구현(조직원 및 권한 검증 -> 예산 값 검증 -> 헤더 제작 -> body 제작 -> 네이버 광고 API 호출 및 결과 반환)

📸 스크린샷 / 테스트 결과 (선택)

결과물 확인을 위한 사진이나 테스트 로그를 첨부해주세요.

  • 수정 전 예산 상태(캠페인, 하루 예산: 60원)
image image
  • 캠페인 예산 수정 요청 (하루 예산: 80원으로 수정 요청)
image - 응답 결과 image image
  • 광고 그룹 예산 상태(기본 입찰가: 70원, 하루 예산 50원)
image
  • 광고 그룹 예산 수정 요청(기본 입찰가: 90원, 하루 예산 80원으로 수정 요청)
image
  • 응답 결과
image image
  • 하루 예산 (dailyBudget)
    하루 동안 광고에 쓸 수 있는 최대 금액. 이 금액을 다 쓰면 그날 광고가 자동으로 멈춤.

  • 기본 입찰가 (bidAmt)
    광고 클릭 1번에 최대 얼마까지 지불할 의향이 있는지 설정하는 금액. 경쟁 광고주보다 입찰가가 높을수록 더 좋은 위치에 광고가 노출됨.

✅ 체크리스트

  • 브랜치 전략(GitHub Flow)을 준수했나요?
  • 메서드 단위로 코드가 잘 쪼개져 있나요?
  • 테스트 통과 확인
  • 서버 실행 확인
  • API 동작 확인

🔍 리뷰 포인트 (Review Points)

리뷰어가 중점적으로 확인했으면 하는 부분을 적어주세요. (P1~P4 적용 가이드)

  • (예: 이 로직이 최선일까요? P2)

  • (예: 예외 처리 누락 여부 확인 부탁드립니다. P1)

  • Ad-Group쪽에는 광고 예산을 저장하는 필드가 없는 걸로 아는데 추가하는 편이 괜찮을까요? 그리고 추가한다면 하루 예산이랑 기본 입찰가 2개다 추가하는 것이 괜찮을까요..? 제 생각에는 둘 다 수정가능한 값이라 둘 다 저장하는 것이 괜찮을 것 같습니다..! 그리고 저희 서비스 내부에서 수정한 값에 대해서 budget_update_at같은 필드를 또 추가해서 예산 수정 시점을 반환하는 것도 괜찮을 것 같습니다..!

💬 리뷰어 가이드 (P-Rules)
P1: 필수 반영 (Critical) - 버그 가능성, 컨벤션 위반. 해결 전 머지 불가.
P2: 적극 권장 (Recommended) - 더 나은 대안 제시. 가급적 반영 권장.
P3: 제안 (Suggestion) - 아이디어 공유. 반영 여부는 드라이버 자율.
P4: 단순 확인/칭찬 (Nit) - 사소한 오타, 칭찬 등 피드백.

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 네이버 캠페인 일일 예산 수정 기능 추가
    • 네이버 광고그룹 예산 및 입찰가 수정 기능 추가
    • 예산 값 유효성 검증 추가 (10의 배수 확인)
    • 조직 관리자 권한 검증 추가
  • Documentation

    • API 문서 업데이트 및 일부 엔드포인트 문서 노출 조정

Review Change Stack

@kingmingyu kingmingyu requested review from jinnieusLab and ojy0903 May 29, 2026 01:52
@kingmingyu kingmingyu self-assigned this May 29, 2026
@kingmingyu kingmingyu added the ✨ Feature 새로운 기능 추가 label May 29, 2026
@kingmingyu kingmingyu linked an issue May 29, 2026 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Warning

Review limit reached

@kingmingyu, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 13 minutes and 6 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d6e6768a-7446-43d9-9e5f-d0c47df7f20a

📥 Commits

Reviewing files that changed from the base of the PR and between 3a30ecf and 5fddf60.

📒 Files selected for processing (3)
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/domain/service/NaverAdApiService.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/exception/code/NaverAdErrorCode.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/presentation/docs/NaverAdApiControllerDocs.java

Walkthrough

캠페인 및 광고그룹 예산을 수정하는 기능을 구현함. 요청 DTO 정의 → Feign 클라이언트 메서드 추가 → 조직 권한 검증과 입력값 검증을 포함한 서비스 로직 → HTTP 엔드포인트 → Swagger 문서화 순서로 진행됨.

Changes

네이버 광고 예산 수정 기능

Layer / File(s) Summary
DTO 계약 및 에러 코드 정의
src/main/java/.../dto/NaverDTO.java, src/main/java/.../exception/code/NaverAdErrorCode.java
캠페인/광고그룹 예산 수정 요청 DTO 2개(Request), API 전송용 바디 DTO 2개(Body), 그리고 예산 검증 실패(NAVER_INVALID_BUDGET_VALUE), 캠페인/광고그룹 예산 수정 실패 에러 코드 2개가 정의됨.
Naver API 클라이언트 통합
src/main/java/.../client/naver/client/NaverClient.java
Feign 기반 NaverClient에 캠페인/광고그룹 예산 수정용 PUT 메서드 2개를 추가. 각 메서드는 헤더, 경로 변수, fields 쿼리 파라미터, 요청 바디를 받아 응답 DTO를 반환하는 계약을 정의함.
서비스 계층 - 권한 검증 및 예산 수정 로직
src/main/java/.../domain/service/NaverAdApiService.java
OrgMemberRepository 주입을 통해 조직 ADMIN 권한을 검증하고, dailyBudget 및 bidAmt의 10의 배수 유효성을 확인한 후, Naver API의 PUT 메서드를 호출하는 updateCampaignBudgetupdateAdGroupBudget 메서드를 구현. 실패 시 전용 에러 코드로 예외 발생.
HTTP API 엔드포인트
src/main/java/.../presentation/NaverAdApiController.java
@AuthenticationPrincipal로 인증 사용자의 userId를 추출하여 PUT /campaigns/{campaignId}/budgetPUT /adgroups/{adgroupId}/budget 엔드포인트를 추가. 서비스 호출 결과를 DataResponse로 감싸 반환.
Swagger API 문서화
src/main/java/.../presentation/docs/NaverAdApiControllerDocs.java
기존 Naver 연동 API 메서드(캠페인/광고그룹/소재/키워드/리포트/동기화 등) 11개에 @Hidden을 적용하여 문서에서 숨기고, 새로운 예산 수정 엔드포인트 2개에 대한 완전한 Swagger 메타데이터(Operation, ApiResponses, 파라미터)를 추가.

Sequence Diagram

sequenceDiagram
  participant Client
  participant NaverAdApiController
  participant NaverAdApiService
  participant OrgMemberRepository
  participant NaverClient
  participant NaverAPI
  
  Client->>NaverAdApiController: PUT /campaigns/{campaignId}/budget
  NaverAdApiController->>NaverAdApiService: updateCampaignBudget(userId, connectionId, campaignId, request)
  NaverAdApiService->>OrgMemberRepository: 조직원 조회
  OrgMemberRepository-->>NaverAdApiService: OrgMember
  NaverAdApiService->>NaverAdApiService: ADMIN 권한 검증
  NaverAdApiService->>NaverAdApiService: dailyBudget % 10 == 0 검증
  NaverAdApiService->>NaverClient: updateCampaignBudget(headers, campaignId, fields, body)
  NaverClient->>NaverAPI: PUT /ncc/campaigns/{campaignId}
  NaverAPI-->>NaverClient: CampaignResponse
  NaverClient-->>NaverAdApiService: CampaignResponse
  NaverAdApiService-->>NaverAdApiController: CampaignResponse
  NaverAdApiController-->>Client: DataResponse<CampaignResponse>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • ojy0903
  • jinnieusLab

📝 리뷰 포인트

이 PR은 엔드-투-엔드 기능 구현으로, 데이터 계약부터 API 노출까지 일관성 있게 구성되어 있습니다. 다음 항목들을 꼼꼼히 확인해주세요:

1️⃣ 권한 검증 로직

  • NaverAdApiServiceupdateCampaignBudget/updateAdGroupBudget 메서드에서 OrgMember 조회 후 ADMIN 권한을 검증하는 부분이 핵심입니다.
  • OrgRole.ADMIN 확인이 명확한지, 권한 없음 시 적절한 예외 코드(OrgErrorCode)가 발생하는지 확인해주세요.

2️⃣ 입력값 검증

  • dailyBudgetbidAmt이 10의 배수인지 검증하는 로직이 있습니다. 이 검증이 모든 사용 케이스를 커버하는지, 에러 메시지가 명확한지 봐주세요.
  • 네이버 API 문서에서 명시한 제약사항이 모두 반영되었는지 확인하면 좋습니다.

3️⃣ Naver API 호출 구성

  • NaverClient의 PUT 메서드 시그니처와 NaverAdApiService에서 호출할 때 전달하는 헤더, 필드, 바디가 일치하는지 확인해주세요.
  • 특히 fields 쿼리 파라미터 값이 네이버 API 문서 스펙과 맞는지 살펴보시면 좋습니다.

4️⃣ DTO 매핑

  • Request (컨트롤러 입력) → Body (Naver API 전송) 매핑 시, UpdateCampaignBudgetRequest의 필드가 UpdateCampaignBudgetBody로 제대로 변환되는지 확인해주세요.
  • 특히 nccCampaignId 필드 값이 어디서 오는지(아마도 connectionId와 관련) 명확한지 봐주세요.

5️⃣ API 문서화

  • 기존 Naver API 메서드들에 @Hidden을 적용한 이유와 범위가 적절한지 확인해주세요. 문서 정리의 의도가 무엇인지 파악하면 좋습니다.
  • 새로운 엔드포인트의 Swagger 메타데이터(Operation, ApiResponses)가 완전하고 명확한지 살펴봐주세요.

6️⃣ 예외 처리

  • 네이버 API 호출 실패 시 AdvertisementHandler 예외가 발생하는데, 에러 코드(NAVER_CAMPAIGN_BUDGET_UPDATE_FAILED, NAVER_AD_GROUP_BUDGET_UPDATE_FAILED)가 구분되어 있습니다. 이것이 클라이언트가 구분할 수 있도록 응답에 반영되는지 확인해주세요.

잘 짜인 점: 🎉

  • DTO 계약이 명확하게 분리되어 있고 (Request vs Body), 엔드-투-엔드 흐름이 일관성 있게 구성되었습니다.
  • 조직 권한 검증이 서비스 계층에서 처리되어 보안이 적절히 강화되었습니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 핵심 변경사항(네이버 광고 예산 수정 API 구현)을 명확하고 간결하게 표현하고 있습니다.
Linked Issues check ✅ Passed 캠페인 및 광고그룹 예산 수정 기능이 #141의 요구사항(캠페인별 예산 수정, 광고그룹별 예산 수정)을 완벽하게 충족하고 있으며, Naver API 명세서 참고하여 구현했습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 예산 수정 기능 구현에 직접 관련되어 있습니다. Swagger 문서 숨김 처리는 기능 구현에 필요한 문서 정리에 해당합니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션(관련 이슈, 개요, 작업 내용, 스크린샷, 체크리스트, 리뷰 포인트)을 포함하고 있으며, 예산 수정 기능에 대해 구체적으로 설명되어 있습니다.

✏️ 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/#141

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 (2)
src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/presentation/NaverAdApiController.java (1)

144-166: 💤 Low value

엔드포인트 구조는 기존 패턴과 잘 맞습니다 👍

@AuthenticationPrincipal(expression = "userId")로 인증 사용자 추출 → 서비스 위임 → DataResponse 래핑까지 컨트롤러는 얇게 유지되어 좋습니다. 한 가지만, 예산/입찰가의 "10의 배수" 검증이 지금은 서비스 안의 수동 if문으로 흩어져 있는데, 향후 DTO에 Bean Validation(예: 커스텀 제약 또는 @Positive)을 붙이고 컨트롤러에서 @Valid로 받으면 검증 위치가 한곳으로 모여 더 선언적이고 일관됩니다. 지금 당장 막는 이슈는 아니라 참고만 해주세요.

🤖 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/main/java/com/whereyouad/WhereYouAd/domains/advertisement/presentation/NaverAdApiController.java`
around lines 144 - 166, Move the "10의 배수" and other budget validation into
declarative Bean Validation: add appropriate validation annotations to
NaverDTO.UpdateCampaignBudgetRequest and NaverDTO.UpdateAdGroupBudgetRequest
(e.g., `@Positive` for >0 and a custom constraint annotation like
`@MultipleOf`(value=10) implemented via ConstraintValidator to enforce multiples
of 10), then annotate the controller parameters in updateCampaignBudget and
updateAdGroupBudget with `@Valid` so Spring validates before calling
naverAdApiService; keep the service logic focused on business rules only and
remove the duplicated manual if-checks from the service methods.
src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/domain/service/NaverAdApiService.java (1)

180-188: ⚡ Quick win

조직원/ADMIN 권한 검증 로직이 두 메서드에 그대로 복붙되어 있어요 (DRY)

180-188 블록과 214-222 블록이 connectionId 조회 → orgId 추출 → ADMIN 검증까지 사실상 동일합니다. 한쪽 정책이 바뀌면 양쪽을 모두 고쳐야 하므로, 공통 private 헬퍼로 추출하는 걸 권장합니다.

♻️ 권한 검증 헬퍼 추출 예시
private void validateAdmin(Long userId, Long connectionId, NaverAdErrorCode notFoundCode) {
    PlatformConnection connection = connectionRepository.findWithAccountAndOrgById(connectionId)
            .orElseThrow(() -> new AdvertisementHandler(notFoundCode));
    Long orgId = connection.getPlatformAccount().getOrganization().getId();
    OrgMember orgMember = orgMemberRepository.findByUserIdAndOrgId(userId, orgId)
            .orElseThrow(() -> new OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND));
    if (orgMember.getRole() != OrgRole.ADMIN) {
        throw new OrgHandler(OrgErrorCode.ORG_MEMBER_FORBIDDEN);
    }
}

As per coding guidelines, "SOLID 원칙, 의존성 주입(DI)" 점검 항목에 따라 중복 제거를 제안합니다.

🤖 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/main/java/com/whereyouad/WhereYouAd/domains/advertisement/domain/service/NaverAdApiService.java`
around lines 180 - 188, Extract the duplicated ADMIN permission checks into a
single private helper (e.g., validateAdmin) and replace both inline blocks with
calls to it; the helper should use
connectionRepository.findWithAccountAndOrgById(connectionId) and throw new
AdvertisementHandler(...) when not found, derive orgId from
connection.getPlatformAccount().getOrganization().getId(), check
orgMemberRepository.findByUserIdAndOrgId(userId, orgId) (throwing new
OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND) if absent) and finally verify
orgMember.getRole() == OrgRole.ADMIN (throw new
OrgHandler(OrgErrorCode.ORG_MEMBER_FORBIDDEN) otherwise), so callers simply call
validateAdmin(userId, connectionId,
NaverAdErrorCode.NAVER_CAMPAIGN_BUDGET_UPDATE_FAILED) or appropriate
notFoundCode.
🤖 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/main/java/com/whereyouad/WhereYouAd/domains/advertisement/domain/service/NaverAdApiService.java`:
- Around line 176-178: Both updateCampaignBudget and updateAdGroupBudget
currently keep a `@Transactional` open while making slow external Naver PUT calls;
split the short DB/permission checks into dedicated methods annotated
`@Transactional`(readOnly = true) (e.g., validateCampaignOwnership,
validateAdGroupOwnership) that perform only the necessary JPA reads, remove
`@Transactional` from updateCampaignBudget and updateAdGroupBudget so the external
HTTP calls run outside any transaction/DB connection, and ensure any required
entities/DTOs from the validation methods are returned for use by the
non-transactional methods.
- Around line 181-182: The repository call
connectionRepository.findWithAccountAndOrgById currently throws an
AdvertisementHandler with NaverAdErrorCode.NAVER_CAMPAIGN_BUDGET_UPDATE_FAILED
(500) when empty; change this to throw a new/not-found-specific
AdvertisementHandler error (add or use a NaverAdErrorCode like
NAVER_CONNECTION_NOT_FOUND mapped to 404) so missing PlatformConnection returns
404, and make the identical change where the same pattern is used in the
ad-group block that calls findWithAccountAndOrgById (around the other
occurrence). Also update the Swagger response documentation sections that
describe these endpoints (the docs around the current 196-202 and 213-217
regions) to include the 404 response and reference the new error code.
- Around line 191-193: The current validation in NaverAdApiService only checks
request.dailyBudget() % 10 == 0 which lets negative multiples pass; update the
validation to require a positive 10-multiple (e.g., if (request.dailyBudget() ==
null || request.dailyBudget() <= 0 || request.dailyBudget() % 10 != 0) throw new
AdvertisementHandler(NaverAdErrorCode.NAVER_INVALID_BUDGET_VALUE)); apply the
same positive 10-multiple check to the bidAmt validation referenced around the
bidAmt handling (lines ~225-230) so negative values are rejected before calling
the Naver API.

---

Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/domain/service/NaverAdApiService.java`:
- Around line 180-188: Extract the duplicated ADMIN permission checks into a
single private helper (e.g., validateAdmin) and replace both inline blocks with
calls to it; the helper should use
connectionRepository.findWithAccountAndOrgById(connectionId) and throw new
AdvertisementHandler(...) when not found, derive orgId from
connection.getPlatformAccount().getOrganization().getId(), check
orgMemberRepository.findByUserIdAndOrgId(userId, orgId) (throwing new
OrgHandler(OrgErrorCode.ORG_MEMBER_NOT_FOUND) if absent) and finally verify
orgMember.getRole() == OrgRole.ADMIN (throw new
OrgHandler(OrgErrorCode.ORG_MEMBER_FORBIDDEN) otherwise), so callers simply call
validateAdmin(userId, connectionId,
NaverAdErrorCode.NAVER_CAMPAIGN_BUDGET_UPDATE_FAILED) or appropriate
notFoundCode.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/presentation/NaverAdApiController.java`:
- Around line 144-166: Move the "10의 배수" and other budget validation into
declarative Bean Validation: add appropriate validation annotations to
NaverDTO.UpdateCampaignBudgetRequest and NaverDTO.UpdateAdGroupBudgetRequest
(e.g., `@Positive` for >0 and a custom constraint annotation like
`@MultipleOf`(value=10) implemented via ConstraintValidator to enforce multiples
of 10), then annotate the controller parameters in updateCampaignBudget and
updateAdGroupBudget with `@Valid` so Spring validates before calling
naverAdApiService; keep the service logic focused on business rules only and
remove the duplicated manual if-checks from the service methods.
🪄 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: 552ea33e-8f31-46ac-a28a-6977c0c8fac5

📥 Commits

Reviewing files that changed from the base of the PR and between 6a59971 and 3a30ecf.

📒 Files selected for processing (6)
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/domain/service/NaverAdApiService.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/exception/code/NaverAdErrorCode.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/presentation/NaverAdApiController.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/presentation/docs/NaverAdApiControllerDocs.java
  • src/main/java/com/whereyouad/WhereYouAd/infrastructure/client/naver/client/NaverClient.java
  • src/main/java/com/whereyouad/WhereYouAd/infrastructure/client/naver/dto/NaverDTO.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 예산 수정- 네이버 광고 예산 수정 API

1 participant