Skip to content

Feat/#94 광고 플랫폼 엔티티 추가 - 광고 플랫폼 API 연동 시 필요한 엔티티 설계#95

Merged
ojy0903 merged 12 commits intodevelopfrom
feat/#94
Mar 27, 2026
Merged

Feat/#94 광고 플랫폼 엔티티 추가 - 광고 플랫폼 API 연동 시 필요한 엔티티 설계#95
ojy0903 merged 12 commits intodevelopfrom
feat/#94

Conversation

@ojy0903
Copy link
Copy Markdown
Collaborator

@ojy0903 ojy0903 commented Mar 25, 2026

📌 관련 이슈

🚀 개요

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

PlatformAccount, PlatformConnection, CampaignPlatform 엔티티 코드 추가 & 기존 MetricFact, AdCampaign 내부 연관관계 주석처리로 추가

image image image

📄 작업 내용

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

  • PlatformAccount, PlatformConnection, CampaignPlatform 엔티티 설계
  • 엔티티들에 대한 기본 JPA repository 인터페이스 추가
  • 기존 MetricFact, AdCampaign 내부에 연관관계 코드 주석으로 추가
  • 디렉터리 구조 설계

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

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

PlatformAccount 테이블
image

PlatformConnection 테이블
image

CampaignPlatform 테이블
image

✅ 체크리스트

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

🔍 리뷰 포인트 (Review Points)

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

  • 광고 플랫폼 연동에 해당 엔티티가 필요할 것 같아 추가해봤습니다. 각 엔티티에 대한 기본 JPA Repositorty 인터페이스랑 디렉터리 구조도 정리해봤습니다.
  • 해당 엔티티들이 ERD 상에서는 PlatformAccount 와 MetricFact 가 1:N 연관관계이고, AdCampaign 과는 N:M 연관관계인데, 해당 MetricFact 와 AdCampaign 에 연관관계를 당장 설정하게 되면 기존 로직에 오류가 발생할 수 있을 것 같아 일단 주석으로 추가해둔 상태입니다. (MetricFact 내부 TODO 주석, AdCampaign 내부 TODO 주석 참고) 어떤식으로 연결해야할지, 만약 연관관계 설정을 진행한다면 기존에 존재하던 Mock data 에서 문제가 생기진 않을 지 잘 모르겠어서 일단 주석처리 된 채로 업로드 합니다...ㅠㅠ 피드백 많이 해주시면 반영해보겠습니다.

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

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 플랫폼 계정 관리 기능 추가로 사용자가 여러 광고 플랫폼을 통합하여 관리 가능
    • 플랫폼 연결 시스템 구현으로 OAuth 및 API 키 기반 인증 지원
    • 광고 캠프인 및 지표 데이터를 플랫폼 계정과 연계하여 추적 가능
    • 통화(KRW, USD), 타임존, 플랫폼 상태 등 설정 옵션 지원

@ojy0903 ojy0903 self-assigned this Mar 25, 2026
@ojy0903 ojy0903 added the ✨ Feature 새로운 기능 추가 label Mar 25, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d14d97ab-04a9-4a31-b8a6-21753455daa4

📥 Commits

Reviewing files that changed from the base of the PR and between 192e9df and 9c271a4.

📒 Files selected for processing (3)
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/AdCampaign.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/MetricFact.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/MetricFact.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/AdCampaign.java

Walkthrough

광고 플랫폼 연동을 위해 새로운 PlatformAccountPlatformConnection JPA 엔티티를 추가하고, 관련 enum 상수와 repository 인터페이스를 정의했습니다. 또한 기존 AdCampaignMetricFact 엔티티에 PlatformAccount로의 관계 매핑을 추가했습니다.

Changes

Cohort / File(s) Summary
새로운 Enum 상수
src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/AuthType.java, Currency.java, PlatformStatus.java, Timezone.java
플랫폼 연동에 필요한 4개 enum 타입 추가: 인증 방식(OAUTH, API_KEY), 통화(KRW, USD), 상태(ACTIVE, DISCONNECTED), 타임존(ASIA, SEOUL)
PlatformAccount 엔티티 및 저장소
src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java, ...repository/PlatformAccountRepository.java
플랫폼 계정 정보를 관리하는 엔티티(외부 계정 ID, 통화, 타임존, 상태, provider 등) 및 JpaRepository 추가
PlatformConnection 엔티티 및 저장소
src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformConnection.java, ...repository/PlatformConnectionRepository.java
플랫폼 인증 정보를 관리하는 엔티티(접근/갱신 토큰, 토큰 만료 시간 등) 및 JpaRepository 추가
기존 엔티티 관계 추가
src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/AdCampaign.java, MetricFact.java
AdCampaign에 platformAccount @ManyToOne 관계, MetricFact에 platformAccount와 adCampaign @ManyToOne 관계 추가

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

🗄️ DB, ✨ Feature

Suggested reviewers

  • kingmingyu
  • jinnieusLab

📝 추가 검토 의견

✅ 잘 설계된 부분

  1. Lazy Loading 적절 사용: 모든 @ManyToOne 관계에서 FetchType.LAZY를 사용하여 불필요한 쿼리 로드를 방지했습니다. 특히 MetricFact가 여러 엔티티와 연관되는 상황에서 좋은 선택입니다.

  2. 기본값(Default) 설정: PlatformAccount의 currency('KRW'), timezone('ASIA'), status('ACTIVE')에 @ColumnDefault를 지정하여 데이터 일관성을 보장했습니다.

  3. 토큰 저장소 길이 설정: PlatformConnection의 accessTokenEnc, refreshTokenEnc를 length=4000으로 설정하여 충분한 용량을 확보했습니다.

⚠️ 검토 필요 사항

  1. N+1 쿼리 위험: MetricFact가 이제 platformAccount와 adCampaign 두 개의 관계를 가집니다. 메트릭 조회 시 페이징이나 필터링이 발생하면:

    // 위험: 메트릭 100개 조회 시 platformAccount 100번, adCampaign 100번 추가 쿼리
    List<MetricFact> metrics = repository.findAll(PageRequest.of(0, 100));
    metrics.forEach(m -> System.out.println(m.getPlatformAccount().getId())); // N+1 발생

    서비스 계층에서 필요시 @EntityGraph 또는 명시적 join fetch를 사용한 조회를 권장합니다.

  2. PlatformAccount와 AdCampaign의 중복 관계: AdCampaign도 platformAccount를 가지고, MetricFact도 platformAccount를 가집니다. 만약 AdCampaign → MetricFact → PlatformAccount 조회 시 중복 데이터 로드 가능성이 있습니다. 인덱싱 전략 검토가 필요합니다.

  3. baseEntity의 audit 칼럼 확인: PlatformAccount와 PlatformConnection이 BaseEntity를 상속했으므로 created_at, updated_at 칼럼이 자동 추가됩니다. 마이그레이션 스크립트에서 이를 고려했는지 확인 바랍니다.

  4. 암호화 필드명: PlatformConnection의 accessTokenEnc, refreshTokenEnc에서 "Enc" 접미사는 관례이지만, 실제 암호화 처리가 구현되어야 합니다. 현재 코드에서는 민감한 데이터(토큰)가 평문 저장될 가능성이 있으므로, AttributeConverter를 활용한 자동 암호화 처리를 권장합니다.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 연동 시 필요한 엔티티 설계'라는 구체적인 내용을 포함하고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 모든 주요 섹션을 포함하고 있습니다: 관련 이슈(#94), 개요(엔티티 추가 설명), 작업 내용(상세 목록), 테스트 결과(테이블 스크린샷), 체크리스트(완료 표시), 리뷰 포인트(상세한 피드백 요청)이 모두 기재되어 있습니다.
Linked Issues check ✅ Passed PR이 링크된 이슈 #94의 요구사항을 대부분 충족합니다. PlatformAccount, PlatformConnection 엔티티 설계 완료, JPA 저장소 인터페이스 추가, MetricFact/AdCampaign의 연관관계 주석 처리 등 이슈의 목표가 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 링크된 이슈 #94의 범위 내에 있습니다. 광고 플랫폼 엔티티 설계, 저장소 인터페이스 추가, enum 상수 정의, 기존 엔티티의 연관관계 주석 처리로 모두 이슈의 목표와 일치합니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#94

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: 5

🧹 Nitpick comments (4)
src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/AdCampaign.java (1)

71-72: 양방향 연관관계 활성화 시 편의 메서드를 같이 넣어주세요.

Line [71]-[72]를 실제로 열 때 addCampaignPlatform/removeCampaignPlatform 메서드로 양쪽(AdCampaignCampaignPlatform.adCampaign)을 함께 세팅하지 않으면, 영속성 컨텍스트에서 관계 불일치가 발생할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/AdCampaign.java`
around lines 71 - 72, Enable the OneToMany relationship (the commented
campaignPlatforms field) and add bidirectional convenience methods on
AdCampaign: implement addCampaignPlatform(CampaignPlatform cp) to initialize
campaignPlatforms if null, add cp to the list and call cp.setAdCampaign(this);
and removeCampaignPlatform(CampaignPlatform cp) to remove from the list and call
cp.setAdCampaign(null); ensure these methods keep both sides in sync to avoid
persistence inconsistencies with CampaignPlatform.adCampaign.
src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/MetricFact.java (1)

66-69: 연관관계 활성화는 “단계적 마이그레이션” 기준을 먼저 고정해두는 게 좋습니다.

Line [66] TODO처럼 보류한 판단은 안전합니다. 다만 실제 활성화 시에는 platform_account_id 백필 → FK/인덱스 추가 → 서비스 코드 전환 순서로 배포 단계를 문서화해 두세요. 이 순서를 안 잡으면 배포 시점 장애가 나기 쉽습니다.

As per coding guidelines, JPA 사용 시 N+1 문제나 불필요한 쿼리가 발생하지 않는지, FetchType.LAZY가 잘 적용되었는지 체크하라.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/MetricFact.java`
around lines 66 - 69, Un-commenting the PlatformAccount relation in MetricFact
must be done with a documented phased migration: add the field annotated with
`@ManyToOne`(fetch = FetchType.LAZY) and `@JoinColumn`(name = "platform_account_id")
in class MetricFact, then perform a data backfill of platform_account_id for
existing rows, add the DB foreign key and index in a separate migration, and
only after those are in place switch service code to use the relation; also
run/verify JPA queries to ensure FetchType.LAZY is honored and no N+1 queries
occur (use joins or fetch graphs where needed) before final deployment.
src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java (2)

58-60: 연관관계 동기화 헬퍼 메서드를 추가하면 누락 저장을 줄일 수 있습니다.

campaignPlatforms만 조작하고 owning side(CampaignPlatform.platformAccount)를 안 맞추면 영속화가 어긋날 수 있습니다. add/remove 헬퍼를 두고 양쪽을 같이 세팅하는 패턴을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`
around lines 58 - 60, Add bidirectional sync helper methods to PlatformAccount:
implement addCampaignPlatform(CampaignPlatform cp) to set
cp.setPlatformAccount(this) and add cp to campaignPlatforms if not present, and
removeCampaignPlatform(CampaignPlatform cp) to remove cp from campaignPlatforms
and call cp.setPlatformAccount(null); ensure null checks and idempotency so the
owning side CampaignPlatform.platformAccount is always kept in sync with the
PlatformAccount.campaignPlatforms collection to avoid missing persistence
updates.

58-60: 연관관계 활성화는 단계적으로 여는 전략이 안전합니다.

현재처럼 중간 엔티티를 먼저 도입한 건 매우 좋은 접근입니다. 다음 단계는

  1. FK/UNIQUE 제약 반영,
  2. 백필/정합성 체크,
  3. 그 다음에 AdCampaign/MetricFact 역방향 연관 오픈
    순서로 가면 기존 mock/로직 영향 최소화에 유리합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`
around lines 58 - 60, The PlatformAccount->CampaignPlatform one-to-many mapping
(campaignPlatforms in PlatformAccount and CampaignPlatform entity) should remain
the single active association for now; do not open the AdCampaign or MetricFact
inverse relationships yet. Instead, add the corresponding FK and UNIQUE
constraints in the schema for CampaignPlatform, implement a backfill/consistency
migration to populate and validate those FKs, and only after successful
validation enable the inverse mappings on AdCampaign and MetricFact; keep
cascade/orphanRemoval behavior on campaignPlatforms as-is but avoid adding
bidirectional getters/setters or `@OneToMany/`@ManyToOne on AdCampaign/MetricFact
until the DB constraints and backfill checks are completed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/Timezone.java`:
- Around line 3-4: Timezone enum currently declares ASIA and SEOUL which don't
represent a concrete IANA timezone and can break time calculations; update the
Timezone enum (replace ASIA and SEOUL) to concrete entries (e.g., ASIA_SEOUL)
that carry the canonical timezone ID/String (like "Asia/Seoul"), add an accessor
(e.g., getZoneId() or toZoneId()) that returns the ZoneId/ID string for
API/storage use, and adjust any persistence/serialization logic that
reads/writes Timezone so it uses the canonical ID accessor instead of the raw
enum name.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/CampaignPlatform.java`:
- Around line 8-27: Add non-null and uniqueness constraints to the N:M join:
update the CampaignPlatform entity to mark the FK join columns non-nullable (set
nullable=false on the `@JoinColumn` for adCampaign and platformAccount) and add a
table-level unique constraint for the combination (e.g.,
`@Table`(name="campaign_platform",
uniqueConstraints=@UniqueConstraint(columnNames={"ad_campaign_id","platform_account_id"})))
to prevent duplicate mappings; also add a DB migration to enforce the NOT NULL
and UNIQUE constraints at the database level.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`:
- Around line 46-48: The PlatformAccount.status column currently declares
length=10 which is too short for values like PlatformStatus.DISCONNECTED (12
chars); update the JPA mapping on the PlatformAccount.status field to increase
the length to at least 12 (or a safe larger value) and add nullable = false to
the `@Column` to prevent nulls, while keeping the `@ColumnDefault`("'ACTIVE'")
unchanged; locate the PlatformAccount class and modify the annotations on the
status field accordingly.
- Around line 35-43: Add NOT NULL constraints to the PlatformAccount entity for
the enum fields by updating the currency and timezone column mappings: for the
fields named currency and timezone in class PlatformAccount, add nullable=false
to their `@Column` annotations (you can keep the existing `@ColumnDefault`("'KRW'")
and `@ColumnDefault`("'ASIA'") if desired) so the database enforces non-null
values like the provider field does.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformConnection.java`:
- Around line 25-28: The PlatformConnection entity is missing DB-level not-null
constraints: add nullable = false to the authType column and to the foreign key
join columns for the user and platformAccount associations; specifically update
the `@Column` on authType (with `@ColumnDefault`("'API_KEY'")) to include nullable =
false, and add nullable = false to the `@JoinColumn` annotations for the user
(user_id) and platformAccount (platform_account_id) fields so that authType,
user_id and platform_account_id are enforced as NOT NULL at the database level.

---

Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/AdCampaign.java`:
- Around line 71-72: Enable the OneToMany relationship (the commented
campaignPlatforms field) and add bidirectional convenience methods on
AdCampaign: implement addCampaignPlatform(CampaignPlatform cp) to initialize
campaignPlatforms if null, add cp to the list and call cp.setAdCampaign(this);
and removeCampaignPlatform(CampaignPlatform cp) to remove from the list and call
cp.setAdCampaign(null); ensure these methods keep both sides in sync to avoid
persistence inconsistencies with CampaignPlatform.adCampaign.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/MetricFact.java`:
- Around line 66-69: Un-commenting the PlatformAccount relation in MetricFact
must be done with a documented phased migration: add the field annotated with
`@ManyToOne`(fetch = FetchType.LAZY) and `@JoinColumn`(name = "platform_account_id")
in class MetricFact, then perform a data backfill of platform_account_id for
existing rows, add the DB foreign key and index in a separate migration, and
only after those are in place switch service code to use the relation; also
run/verify JPA queries to ensure FetchType.LAZY is honored and no N+1 queries
occur (use joins or fetch graphs where needed) before final deployment.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`:
- Around line 58-60: Add bidirectional sync helper methods to PlatformAccount:
implement addCampaignPlatform(CampaignPlatform cp) to set
cp.setPlatformAccount(this) and add cp to campaignPlatforms if not present, and
removeCampaignPlatform(CampaignPlatform cp) to remove cp from campaignPlatforms
and call cp.setPlatformAccount(null); ensure null checks and idempotency so the
owning side CampaignPlatform.platformAccount is always kept in sync with the
PlatformAccount.campaignPlatforms collection to avoid missing persistence
updates.
- Around line 58-60: The PlatformAccount->CampaignPlatform one-to-many mapping
(campaignPlatforms in PlatformAccount and CampaignPlatform entity) should remain
the single active association for now; do not open the AdCampaign or MetricFact
inverse relationships yet. Instead, add the corresponding FK and UNIQUE
constraints in the schema for CampaignPlatform, implement a backfill/consistency
migration to populate and validate those FKs, and only after successful
validation enable the inverse mappings on AdCampaign and MetricFact; keep
cascade/orphanRemoval behavior on campaignPlatforms as-is but avoid adding
bidirectional getters/setters or `@OneToMany/`@ManyToOne on AdCampaign/MetricFact
until the DB constraints and backfill checks are completed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c28280ce-acf9-4e08-a2dc-3d48b68f8c72

📥 Commits

Reviewing files that changed from the base of the PR and between cbaba85 and 2fa83b0.

📒 Files selected for processing (19)
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/AdCampaign.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/MetricFact.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/application/dto/.gitkeep
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/application/mapper/.gitkeep
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/AuthType.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/Currency.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/PlatformStatus.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/Timezone.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/service/.gitkeep
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/exception/.gitkeep
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/exception/code/.gitkeep
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/CampaignPlatform.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformConnection.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/repository/CampaignPlatformRepository.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/repository/PlatformAccountRepository.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/repository/PlatformConnectionRepository.java
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/presentation/.gitkeep
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/presentation/docs/.gitkeep

Comment on lines +3 to +4
public enum Timezone {
ASIA, SEOUL
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Timezone 값이 실제 타임존 의미를 정확히 담지 못합니다.

Line [4]의 ASIA, SEOUL은 단일 타임존(Asia/Seoul)을 표현하기 어려워, 저장값/조회값 해석이 깨질 가능성이 큽니다. 플랫폼 API 연동 시 시간 집계 오차로 이어질 수 있어 지금 단계에서 정리하는 게 안전합니다.

🔧 제안 수정안
 public enum Timezone {
-    ASIA, SEOUL
+    ASIA_SEOUL("Asia/Seoul");
+
+    private final String zoneId;
+
+    Timezone(String zoneId) {
+        this.zoneId = zoneId;
+    }
+
+    public String zoneId() {
+        return zoneId;
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/Timezone.java`
around lines 3 - 4, Timezone enum currently declares ASIA and SEOUL which don't
represent a concrete IANA timezone and can break time calculations; update the
Timezone enum (replace ASIA and SEOUL) to concrete entries (e.g., ASIA_SEOUL)
that carry the canonical timezone ID/String (like "Asia/Seoul"), add an accessor
(e.g., getZoneId() or toZoneId()) that returns the ZoneId/ID string for
API/storage use, and adjust any persistence/serialization logic that
reads/writes Timezone so it uses the canonical ID accessor instead of the raw
enum name.

Comment on lines +8 to +27
@Table(name = "campaign_platform")
@Getter
public class CampaignPlatform {

//AdCampaign - PlatformAccount 간 N:M 매핑을 위한 중간 테이블

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "campaign_platform_id")
private Long id;

// AdCampaign과의 N:1 관계
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ad_campaign_id")
private AdCampaign adCampaign;

// PlatformAccount와의 N:1 관계
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "platform_account_id")
private PlatformAccount platformAccount;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find src/main/java -type f -name "CampaignPlatform.java" | head -5

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 170


🏁 Script executed:

find . -type f -name "*.java" -path "*/platform/persistence/entity/*" | head -20

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 373


🏁 Script executed:

find . -type f \( -name "*Migration*" -o -name "*Schema*" -o -name "*.sql" \) | head -20

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 55


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/CampaignPlatform.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 1063


🏁 Script executed:

find src/main/java -type f -name "AdCampaign.java"

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 169


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/advertisement/persistence/entity/AdCampaign.java | head -100

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 2973


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 2527


🏁 Script executed:

find src/main/java -type f -name "*CampaignPlatform*Repository*" -o -name "*CampaignPlatform*Service*"

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 184


🏁 Script executed:

rg "CampaignPlatform" src/main/java --type java -A 3 -B 1 | head -100

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 3548


🏁 Script executed:

rg "CampaignPlatform" src/main/java/com/whereyouad/WhereYouAd/domains/platform --type java -A 5 | grep -E "(save|create|add|where|find)" -i

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 1887


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/repository/CampaignPlatformRepository.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 441


N:M 중간 테이블의 데이터 무결성 제약을 추가해야 합니다.

현재 FK 컬럼들(ad_campaign_id, platform_account_id)이 기본값으로 nullable 상태이며 중복 방지 제약이 없어서, 같은 조합의 매핑이 여러 번 저장될 수 있습니다. 예를 들어 AdCampaign(1번)과 PlatformAccount(10번)의 매핑이 실수로 2개 저장되면, 집계나 조회 시 잘못된 결과가 나올 수 있습니다.

PlatformAccount에 cascade와 orphanRemoval이 잘 설정되어 있는 것은 좋으나, 테이블 수준의 제약도 필요합니다:

제안 수정안
-@Table(name = "campaign_platform")
+@Table(
+    name = "campaign_platform",
+    uniqueConstraints = `@UniqueConstraint`(
+        name = "uk_campaign_platform_campaign_account",
+        columnNames = {"ad_campaign_id", "platform_account_id"}
+    )
+)
 `@Getter`
 public class CampaignPlatform {
@@
-    `@JoinColumn`(name = "ad_campaign_id")
+    `@JoinColumn`(name = "ad_campaign_id", nullable = false)
     private AdCampaign adCampaign;
@@
-    `@JoinColumn`(name = "platform_account_id")
+    `@JoinColumn`(name = "platform_account_id", nullable = false)
     private PlatformAccount platformAccount;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/CampaignPlatform.java`
around lines 8 - 27, Add non-null and uniqueness constraints to the N:M join:
update the CampaignPlatform entity to mark the FK join columns non-nullable (set
nullable=false on the `@JoinColumn` for adCampaign and platformAccount) and add a
table-level unique constraint for the combination (e.g.,
`@Table`(name="campaign_platform",
uniqueConstraints=@UniqueConstraint(columnNames={"ad_campaign_id","platform_account_id"})))
to prevent duplicate mappings; also add a DB migration to enforce the NOT NULL
and UNIQUE constraints at the database level.

Comment on lines +35 to +43
@Enumerated(EnumType.STRING)
@Column(name = "currency", length = 10)
@ColumnDefault("'KRW'")
private Currency currency;

@Enumerated(EnumType.STRING)
@Column(name = "timezone", length = 64)
@ColumnDefault("'ASIA'")
private Timezone timezone;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "PlatformAccount.java" | head -5

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 171


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 2527


🏁 Script executed:

# Check how currency and timezone are used in the codebase
rg "currency|timezone" -t java src/main/java/com/whereyouad/WhereYouAd/domains/platform/ -A 2 -B 2

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 1545


🏁 Script executed:

# Check if there are constructors or builders that always initialize these fields
ast-grep --pattern $'class PlatformAccount {
  $$$
  PlatformAccount($$$) {
    $$$
  }
  $$$
}'

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 55


🏁 Script executed:

# Check for any migration files or database schema definitions
find . -type f -name "*.sql" -o -name "*migration*" | grep -i platform | head -10

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 55


🏁 Script executed:

find . -type f -name "*.java" -path "*platform*service*" | head -10

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 55


🏁 Script executed:

# Search for PlatformAccount creation patterns
rg "PlatformAccount" -t java --max-count=20 -B 2 -A 5 | grep -E "\.builder|new PlatformAccount|\.currency|\.timezone" | head -30

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 55


🏁 Script executed:

# Check the Currency and Timezone enums to understand their values
cat -n src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/Currency.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 214


🏁 Script executed:

cat -n src/main/java/com/whereyouad/WhereYouAd/domains/platform/domain/constant/Timezone.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 217


플랫폼 계정의 핵심 속성 currency, timezone에 데이터베이스 제약 추가 필요

@ColumnDefault만으로는 NULL 삽입을 방지할 수 없습니다. 데이터베이스 레벨에서 기본값이 설정되지만, 명시적으로 NULL을 INSERT하면 그대로 저장됩니다. 같은 엔티티의 provider 필드처럼 nullable=false를 추가해 데이터 무결성을 보장하세요.

-    `@Column`(name = "currency", length = 10)
+    `@Column`(name = "currency", length = 10, nullable = false)
     `@ColumnDefault`("'KRW'")
     private Currency currency;

-    `@Column`(name = "timezone", length = 64)
+    `@Column`(name = "timezone", length = 64, nullable = false)
     `@ColumnDefault`("'ASIA'")
     private Timezone timezone;

예를 들어, 현재 상태라면 INSERT INTO platform_account(currency) VALUES(NULL)이 성공하지만, nullable=false를 추가하면 DB 제약으로 차단됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`
around lines 35 - 43, Add NOT NULL constraints to the PlatformAccount entity for
the enum fields by updating the currency and timezone column mappings: for the
fields named currency and timezone in class PlatformAccount, add nullable=false
to their `@Column` annotations (you can keep the existing `@ColumnDefault`("'KRW'")
and `@ColumnDefault`("'ASIA'") if desired) so the database enforces non-null
values like the provider field does.

Comment on lines +25 to +28
@Enumerated(EnumType.STRING)
@Column(name = "auth_type")
@ColumnDefault("'API_KEY'")
private AuthType authType;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "PlatformConnection.java" -type f

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 174


🏁 Script executed:

cat -n ./src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformConnection.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 1822


🏁 Script executed:

# Check if there are any validation annotations in the file
rg "@NotNull|@NotBlank|@Valid" ./src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformConnection.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 55


🏁 Script executed:

# Check User and PlatformAccount entities to understand the relationships
cat -n ./src/main/java/com/whereyouad/WhereYouAd/domains/user/persistence/entity/User.java | head -50

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 1861


🏁 Script executed:

# Check PlatformAccount entity
find . -name "PlatformAccount.java" -type f

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 171


🏁 Script executed:

cat -n ./src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java

Repository: WhereYouAd/WhereYouAd-Backend

Length of output: 2527


필수 도메인/FK 컬럼에 nullable = false를 명시해주세요.

Line 26, Line 43, Line 47의 authType, user_id, platform_account_id 컬럼에 현재 nullable = false 제약이 없습니다.

PlatformConnection 엔티티의 특성상 이 세 필드는 반드시 필요한 데이터입니다:

  • authType: @ColumnDefault("'API_KEY'") 지정되어 있어 항상 값이 있어야 하고
  • user_id, platform_account_id: 외래키로서 연결이 성립하려면 필수적입니다

같은 코드베이스의 User, PlatformAccount 엔티티처럼 명시적으로 nullable = false를 선언하면, DB 레벨에서도 데이터 무결성이 보장됩니다. 현재는 애플리케이션 단에서만 검증되고 있어 DB 직접 조작 시 잠재적 위험이 있습니다.

제안 수정안
-    `@Column`(name = "auth_type")
+    `@Column`(name = "auth_type", nullable = false)
     `@ColumnDefault`("'API_KEY'")
     private AuthType authType;

-    `@JoinColumn`(name = "user_id")
+    `@JoinColumn`(name = "user_id", nullable = false)
     private User user;

-    `@JoinColumn`(name = "platform_account_id")
+    `@JoinColumn`(name = "platform_account_id", nullable = false)
     private PlatformAccount platformAccount;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformConnection.java`
around lines 25 - 28, The PlatformConnection entity is missing DB-level not-null
constraints: add nullable = false to the authType column and to the foreign key
join columns for the user and platformAccount associations; specifically update
the `@Column` on authType (with `@ColumnDefault`("'API_KEY'")) to include nullable =
false, and add nullable = false to the `@JoinColumn` annotations for the user
(user_id) and platformAccount (platform_account_id) fields so that authType,
user_id and platform_account_id are enforced as NOT NULL at the database level.

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: 1

🧹 Nitpick comments (1)
src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java (1)

50-52: provider 컬럼에 length 명시 권장

다른 enum 필드들(currency, timezone, status)은 모두 length를 명시했는데, provider만 누락되어 있습니다. 현재 Provider enum의 값들(GOOGLE, KAKAO, NAVER)이 모두 6자 이하이므로, length = 20으로 설정하면 충분하고 status 필드와의 일관성도 유지할 수 있습니다.

♻️ 수정 제안
 `@Enumerated`(EnumType.STRING)
-@Column(name = "provider", nullable = false)
+@Column(name = "provider", length = 20, nullable = false)
 private Provider provider;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`
around lines 50 - 52, The provider column on PlatformAccount is missing a length
attribute; update the `@Column` annotation on the private Provider provider field
in class PlatformAccount to include length = 20 (e.g., `@Column`(name =
"provider", nullable = false, length = 20)) so it matches other enum fields
(currency, timezone, status) and accommodates values like GOOGLE/KAKAO/NAVER;
keep the `@Enumerated`(EnumType.STRING) as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`:
- Around line 19-21: The PlatformAccount class is vulnerable to NPEs because
fields initialized with new ArrayList<>() are ignored by Lombok's `@Builder`;
update each such field (e.g., campaignPlatforms and the other fields noted at
lines 58-60) to use `@Builder.Default` on the field declaration so the default
initializers are preserved when created via PlatformAccount.builder().build();
keep the existing initializer (new ArrayList<>()) and add the `@Builder.Default`
annotation immediately above each field.

---

Nitpick comments:
In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`:
- Around line 50-52: The provider column on PlatformAccount is missing a length
attribute; update the `@Column` annotation on the private Provider provider field
in class PlatformAccount to include length = 20 (e.g., `@Column`(name =
"provider", nullable = false, length = 20)) so it matches other enum fields
(currency, timezone, status) and accommodates values like GOOGLE/KAKAO/NAVER;
keep the `@Enumerated`(EnumType.STRING) as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a60a65dd-91e1-4520-a6d5-e22e20e55cb6

📥 Commits

Reviewing files that changed from the base of the PR and between 2fa83b0 and 192e9df.

📒 Files selected for processing (1)
  • src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java

Comment on lines +19 to +21
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Lombok @Builder와 필드 초기화 값 충돌 - NPE 위험

@Builder를 사용할 때 필드 초기화 값(new ArrayList<>())은 무시됩니다. 예를 들어:

// 이렇게 빌더로 생성하면 campaignPlatforms가 null이 됩니다
PlatformAccount account = PlatformAccount.builder()
    .externalAccountId("test")
    .provider(Provider.NAVER)
    .build();

account.getCampaignPlatforms().add(...); // NPE 발생!

@Builder.Default 어노테이션을 추가해 빌더 사용 시에도 기본값이 적용되도록 해주세요.

🐛 수정 제안
 //AdCampaign 과의 다대다 매핑을 위한 중간 테이블과 1:N 매핑
 `@OneToMany`(mappedBy = "platformAccount", cascade = CascadeType.ALL, orphanRemoval = true)
+@Builder.Default
 private List<CampaignPlatform> campaignPlatforms = new ArrayList<>();

Also applies to: 58-60

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/whereyouad/WhereYouAd/domains/platform/persistence/entity/PlatformAccount.java`
around lines 19 - 21, The PlatformAccount class is vulnerable to NPEs because
fields initialized with new ArrayList<>() are ignored by Lombok's `@Builder`;
update each such field (e.g., campaignPlatforms and the other fields noted at
lines 58-60) to use `@Builder.Default` on the field declaration so the default
initializers are preserved when created via PlatformAccount.builder().build();
keep the existing initializer (new ArrayList<>()) and add the `@Builder.Default`
annotation immediately above each field.

Copy link
Copy Markdown
Collaborator

@kingmingyu kingmingyu left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! 🔥🔥 항상 빠르게 작업해주셔서 감사합니다..!!👍 아래에 제 생각 간단하게 적어보았습니다..!

Copy link
Copy Markdown
Collaborator

@jinnieusLab jinnieusLab left a comment

Choose a reason for hiding this comment

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

P4: 고생하셨습니다! 검토하다보니 저희 짜놓은 ERD가 좀 수정되어야할 듯 하네요...
정리해보자면 제 의견은 일단 이런 식으로 수정하면 괜찮을 것 같아요..!

Organization ➔ Project (1:N) (유지)

Organization ➔ PlatformAccount (1:N) (변경)

PlatformAccount ➔ PlatformConnection (1:N) (유지)
PlatformAccount ➔ AdCampaign (1:N) (변경, CampaignPlatform 삭제)
Project ➔ AdCampaign (1:N) (유지)

PlatformAccount ➔ MetricFact (1:N) (주석 해제)
AdCampaign ➔ MetricFact (1:N) (추가)

@ojy0903
Copy link
Copy Markdown
Collaborator Author

ojy0903 commented Mar 27, 2026

@kingmingyu @jinnieusLab 피드백 감사합니다!! 종합적으로 반영해서 다음과 같이 수정해봤습니다. 한번 확인해보시고 괜찮으면 Approve 남겨주시면 머지 하겠습니다!

  1. CampaignPlatform 엔티티와 Repository 제거
  2. Organization - PlatformAccount 간 1:N 연결을 위해 PlatformAccount 에 Organization 필드 추가
  3. PlatformAccount - AdCampaign 간 1:N 연결 위해 AdCampaign 내부 PlatformAccount 필드 추가
  4. PlatformAccount - MetricFact 간 1:N 연결, AdCampaign - MetricFact 간 1:N 연결을 위해 MetricFact 내부 PlatformAccount, AdCampaign 필드 추가

Copy link
Copy Markdown
Collaborator

@jinnieusLab jinnieusLab left a comment

Choose a reason for hiding this comment

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

P4: 네 확인했습니다 깔끔하게 잘 마무리해주신 것 같아요! 고생하셨습니다!

@kingmingyu
Copy link
Copy Markdown
Collaborator

P4: 확인했습니다!! 고생하셨습니다!!🔥🔥

@ojy0903 ojy0903 merged commit e1d977f into develop Mar 27, 2026
2 checks passed
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 연동 시 필요한 엔티티 설계

3 participants