[Feat] ad.picke.store 제휴 광고(쿠팡 파트너스·애드픽) 도메인 및 API - #435
Conversation
- ad 도메인 신설: 소재(AdCreative), 클릭 로그, 일별 노출 집계
- 앱 API: GET /api/v1/ads (지면별 가중 로테이션), POST /api/v1/ads/impressions
- 클릭 리다이렉트: GET /c/{code} -> 매체별 추적 파라미터 병합 후 302, 클릭 비동기 적재
- 관리자 API: 소재 CRUD, 소재별 노출/클릭/CTR, 클릭 내역 목록
- 쿠팡은 subId를 병합해 지면별 성과를 분리하고, 애드픽은 규격 확인 전까지 pass-through
- ad.picke.store 루트 공개 지면 추가 (매체 심사에서 접속 확인 대상)
- Swagger "3. 광고 API" 그룹으로 분리
- JwtFilter에 정확 일치 화이트리스트를 분리해 루트·/error가 401로 막히던 문제 해결
- 지면을 iOS 실제 화면 기준 5개로 확장하고 CPI 허용 여부를 지면 속성으로 표기 - 애드픽 서브아이디 파라미터를 picke.ad.adpick.sub-id-param 설정값으로 분리 규격 확인 시 배포 없이 환경변수만 채우면 지면별 성과 분리가 켜진다 - 소재 등록·수정 시 제휴 링크의 lptag를 coupang.partners.id와 대조 단축 링크에는 lptag가 없으므로 파라미터가 있을 때만 검사한다 - 링크 병합 로직을 AffiliateLinks로 추출해 매체별 빌더가 공유 - 마이그레이션 SQL은 참고용임을 명시 (운영은 ddl-auto=update로 자동 생성)
BattleVoteServiceImpl은 "오늘"을 LocalDate.now(KST)로 판단하는데 테스트는 시스템 기본 시간대의 LocalDate.now()를 썼다. UTC 러너에서는 15:00 UTC(=KST 자정) 이후로 두 값이 하루씩 어긋나 오늘 배틀이 어제 배틀로 취급되면서 크레딧 차감 분기가 반대로 탔다. 14:42 UTC 실행은 통과하고 15:25 UTC 실행은 실패한 이유다. 테스트도 KST 기준으로 맞춘다. 광고 작업과는 무관한 기존 결함이지만 CI를 막고 있어 함께 고친다.
진입점에서 TimeZone.setDefault(Asia/Seoul)을 하고 있어 런타임 동작은 문제없었지만, 기본 시간대 설정에 기대면 일별 노출 버킷이 엉뚱한 시각에 갈릴 수 있다. BattleVoteServiceImpl과 같은 방식으로 KST를 명시한다.
|
기능 자체는 잘 보이는데 몇 가지 짚고 갈 게 있어서 정리해봤어요. 1. enum 컬럼에 CHECK 제약조건이 없음 2. 3. 주석에 4. 이슈/PR/브랜치 템플릿 안 지킴 5. 6. 쿠팡 파트너스 ID가 하드코딩된 기본값으로 들어가있음 7. 8. 9. 전체적으로 기능 설계나 로직은 꼼꼼하게 잘 짜신 것 같아요! 고생하셨습니당 |
si-zero
left a comment
There was a problem hiding this comment.
실제로 깃허브에 올라가면 안되는 파일들이 보입니다. 위 리뷰 확인하셔서 수정 부탁드립니다!
요약
앱 지면에 제휴 광고 배너를 네이티브로 노출하고, 탭하면 외부 브라우저로 제휴 링크에 다이렉트되는 구조를 붙였습니다. 매체는 쿠팡 파트너스와 애드픽 두 곳입니다.
설계 문서:
docs/superpowers/specs/2026-09-01-ad-picke-store-design.md동작
GET /api/v1/ads?slot=)POST /api/v1/ads/impressions)GET /c/{code}→ 매체별 추적 파라미터 병합 후 302, 클릭은 비동기 적재주요 결정
지면은 테이블이 아니라 enum입니다. 앱이 그릴 줄 모르는 지면을 어드민에서 만들어봐야 아무 일도 일어나지 않습니다. 지면 추가는 어차피 앱 배포와 묶입니다.
노출은 raw 로그로 쌓지 않습니다. 배너가 스크롤에 걸릴 때마다 행이 생기면 금방 수천만 건이 됩니다. 일별 카운터 upsert로 CTR을 뽑는 데 충분합니다.
클릭 로그는 익명입니다.
/c/{code}는 외부 브라우저에서 열려 Authorization 헤더가 없습니다. 공개 URL에 사용자 식별자를 실으면 열거 공격과 프라이버시 문제가 생기고, 지면별 CTR과 정산 대조에는 userId가 필요 없습니다.쿠팡은 subId를 병합하고 애드픽은 pass-through입니다. 쿠팡은 subId로 지면별 실매출이 파트너스 리포트에서 갈립니다. 애드픽은 서브아이디 파라미터 규격을 아직 확인하지 못해 원본 링크를 그대로 넘깁니다. 지면별 성과 분리만 안 될 뿐 노출·클릭·리다이렉트는 정상 동작하고, 규격이 확인되면
AdpickLinkBuilder에 파라미터명만 채우면 됩니다.곁들여 고친 것
JwtFilter가 SecurityConfig보다 먼저 돌면서 토큰이 없으면 무조건 401을 던지고 있었습니다. 게다가isWhitelisted가startsWith매칭이라"/"를 넣으면 전체 인증이 무력화됩니다. 정확히 일치할 때만 통과하는EXACT_WHITELIST를 분리해/와/error를 넣었습니다./error가 빠져 있던 탓에 존재하지 않는 모든 경로가 404 대신 401로 나오고 있었습니다. 실제로https://ad.picke.store/가 인증 오류 JSON을 반환하던 원인입니다.Swagger
광고 API는
3. 광고 API그룹으로 분리했습니다. 기존 사용자 그룹은FE_USED_OPERATIONS화이트리스트로 필터링되므로 거기 넣으면 어차피 보이지 않습니다.배포 전 확인
docs/db/20260901_create_ad_tables.sql실행 필요AdSlotCode) 목록은 앱팀 확정 후 채워야 합니다. 현재HOME_FEED,BATTLE_RESULT_BOTTOM두 개테스트
광고 도메인 테스트 통과 (링크 병합, 게재 기간 필터, 가중 로테이션, 노출 upsert, 만료 코드 처리).
전체 스위트에서
ScenarioAudioPipelineServiceTest와ScenarioServiceImplTest2건이 실패하지만, 이 브랜치 변경을 걷어낸 상태에서도 동일하게 실패하는 기존 이슈입니다.