Skip to content

refactor: ci/cd 오류 수정 flyway 버전 - #1448

Merged
KEEKE132 merged 10 commits into
devfrom
refactor/#1439-noti
Aug 6, 2026
Merged

refactor: ci/cd 오류 수정 flyway 버전#1448
KEEKE132 merged 10 commits into
devfrom
refactor/#1439-noti

Conversation

@KEEKE132

@KEEKE132 KEEKE132 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🚩 관련사항

dev 배포 수정

📢 전달사항

flyway 버전 순서로 인한 문제
새 스크립트 버전을 올렸습니다.

📸 스크린샷

관련한 스크린샷을 첨부해주세요.

📃 진행사항

  • 수정

⚙️ 기타사항

기타 참고사항을 적어주세요.

개발기간:

Summary by CodeRabbit

  • 새로운 기능

    • 게시물 좋아요 수가 주요 마일스톤에 도달할 때만 알림이 생성됩니다.
    • 마일스톤 달성 기록을 저장하고 중복 알림을 방지합니다.
    • 알림 설정, 차단 상태, 본인 좋아요 및 게시물 이용 가능 여부를 확인해 알림을 제한합니다.
    • 조건을 충족한 경우 마일스톤 달성 푸시 알림을 발송합니다.
  • 변경 사항

    • 일반적인 게시물 좋아요 알림은 제거되고, 마일스톤 기반 알림으로 통합되었습니다.

@github-actions
github-actions Bot requested a review from riadan710 August 6, 2026 13:03
@github-actions github-actions Bot added the D-3 label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

좋아요 저장 후 마일스톤에 해당할 때만 달성 이벤트를 발행합니다. 달성 이력을 중복 없이 저장하고, 대상 상태·설정·차단 여부를 검증한 뒤 알림과 푸시를 커밋 후 비동기로 처리합니다.

Changes

게시물 좋아요 마일스톤 알림

Layer / File(s) Summary
마일스톤 판정과 이벤트 발행
app-main/src/main/java/net/causw/app/main/domain/community/post/service/LikePostService.java, app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/policy/LikePostNotificationPolicy.java, app-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikeMilestoneReachedEvent.java, app-main/src/test/.../LikePostServiceTest.java, app-main/src/test/.../LikePostNotificationPolicyTest.java
좋아요 수가 정책상 마일스톤일 때만 PostLikeMilestoneReachedEvent를 발행합니다. 기존 PostLikedEvent 흐름을 제거했습니다.
달성 이력 저장과 상태 관리
app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java, app-main/src/main/java/net/causw/app/main/domain/notification/notification/repository/PostLikeMilestoneAchievementRepository.java, app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievement*, app-main/src/test/.../notification/...
마일스톤별 달성 이력을 저장합니다. 중복을 방지하고 PENDING, BASELINED, NOTIFICATION_CREATED, SUPPRESSED 상태를 관리합니다.
알림 조건 검증과 생성
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.java, app-main/src/test/.../PostLikeMilestoneNotificationProcessorTest.java
게시물과 작성자 상태, 자기 좋아요, 알림 설정, 차단 관계를 검증합니다. 조건에 따라 억제 사유를 기록하거나 알림과 로그를 저장합니다.
커밋 후 비동기 푸시 전달
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestone*Listener.java, app-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikeMilestonePushEvent.java, app-main/src/test/.../notification/.../listener/*
달성 이벤트와 푸시 이벤트를 AFTER_COMMIT 및 비동기 트랜잭션으로 처리합니다. 유효한 알림만 수신자에게 푸시합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LikePostService
  participant AchievementRecorder
  participant NotificationProcessor
  participant PushListener
  LikePostService->>AchievementRecorder: 마일스톤 달성 이벤트 전달
  AchievementRecorder-->>NotificationProcessor: 달성 이력 ID 전달
  NotificationProcessor->>NotificationProcessor: 대상 상태와 알림 조건 검증
  NotificationProcessor->>PushListener: 푸시 이벤트 발행
  PushListener-->>PushListener: 수신자 조회 및 푸시 전송
Loading

Possibly related PRs

  • CAUCSE/CAUSW_backend#1440: LikePostService와 게시물 좋아요 마일스톤 알림 흐름을 동일하게 변경한 PR입니다.

Suggested reviewers: glucosei

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 PR 목표에 포함된 Flyway 버전 수정과 관련되지만, 변경사항의 주요 내용인 좋아요 마일스톤 알림 흐름을 설명하지 않습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#1439-noti

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.

@bingle625 bingle625 assigned bingle625 and KEEKE132 and unassigned bingle625 Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Test Results Summary

184 files  184 suites   21s ⏱️
626 tests 626 ✅ 0 💤 0 ❌
646 runs  646 ✅ 0 💤 0 ❌

Results for commit 543b5aa.

@KEEKE132
KEEKE132 merged commit f22c06c into dev Aug 6, 2026
11 of 12 checks passed
@KEEKE132
KEEKE132 deleted the refactor/#1439-noti branch August 6, 2026 13:12

@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: 4

🧹 Nitpick comments (4)
app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListenerTest.java (1)

54-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

비동기와 새 트랜잭션 계약도 검증하세요.

현재 테스트는 TransactionPhase.AFTER_COMMIT만 검증합니다. @Async("asyncExecutor") 또는 @Transactional(propagation = Propagation.REQUIRES_NEW)가 제거되어도 테스트가 통과합니다.

두 애너테이션과 설정값을 함께 검증하세요.

검증 코드 예시
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
  TransactionalEventListener annotation = handleMethod.getAnnotation(TransactionalEventListener.class);
+ Async asyncAnnotation = handleMethod.getAnnotation(Async.class);
+ Transactional transactionalAnnotation = handleMethod.getAnnotation(Transactional.class);

  assertThat(annotation).isNotNull();
  assertThat(annotation.phase()).isEqualTo(TransactionPhase.AFTER_COMMIT);
+ assertThat(asyncAnnotation).isNotNull();
+ assertThat(asyncAnnotation.value()).isEqualTo("asyncExecutor");
+ assertThat(transactionalAnnotation).isNotNull();
+ assertThat(transactionalAnnotation.propagation()).isEqualTo(Propagation.REQUIRES_NEW);

As per path instructions, "테스트 케이스가 충분한지 확인합니다 (성공/실패/경계 케이스)."

🤖 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
`@app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListenerTest.java`
around lines 54 - 64, Update handle_shouldRunAfterCommit in
PostLikeMilestonePushListenerTest to also retrieve and assert the listener
method’s `@Async` annotation value is "asyncExecutor" and its `@Transactional`
annotation propagation is Propagation.REQUIRES_NEW, while preserving the
existing AFTER_COMMIT assertion.

Source: Path instructions

app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListener.java (1)

21-26: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

비동기 리스너에서 예외를 로깅해 주세요.

handle@Asyncvoid 메서드입니다. 예외가 발생하면 호출 스택으로 전파되지 않고 AsyncUncaughtExceptionHandler로 넘어갑니다. 전역 핸들러가 없으면 실패 원인이 남지 않습니다. 좋아요 트랜잭션은 이미 커밋된 상태이므로 사용자 요청에도 오류가 드러나지 않습니다.

SLF4J로 실패를 기록해 주세요. 이력 ID와 게시글 ID를 남기면 재처리 시 추적이 쉬워집니다.

♻️ 제안 변경
+import lombok.extern.slf4j.Slf4j;
+
 `@Component`
+@Slf4j
 `@RequiredArgsConstructor`
 public class PostLikeMilestoneReachedListener {
 
 	private final PostLikeMilestoneAchievementRecorder achievementRecorder;
 	private final PostLikeMilestoneNotificationProcessor notificationProcessor;
 
 	`@Async`("asyncExecutor")
 	`@TransactionalEventListener`(phase = TransactionPhase.AFTER_COMMIT)
 	public void handle(PostLikeMilestoneReachedEvent event) {
-		achievementRecorder.record(event)
-			.ifPresent(notificationProcessor::process);
+		try {
+			achievementRecorder.record(event)
+				.ifPresent(notificationProcessor::process);
+		} catch (Exception exception) {
+			log.error("게시글 좋아요 마일스톤 알림 처리 실패. postId={}, milestoneCount={}",
+				event.postId(), event.milestoneCount(), exception);
+		}
+	}

민감 정보를 남기지 않도록 likerId는 로그에서 제외했습니다.

As per path instructions: "로깅은 SLF4J를 사용하고 System.out.println은 절대 사용하지 않습니다."

🤖 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
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListener.java`
around lines 21 - 26, Update PostLikeMilestoneReachedListener.handle to catch
exceptions from the asynchronous
achievementRecorder.record/notificationProcessor flow and log them through an
SLF4J logger. Include the history ID and post ID in the failure log for
traceability, exclude likerId, and preserve the existing processing behavior.

Source: Path instructions

app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.java (1)

35-40: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

PENDING 상태로 남은 이력의 복구 경로를 마련해 주세요(운영 조언).

process가 중간에 예외로 실패하면 이력은 PENDING으로 남습니다. 현재 흐름에는 재시도 주체가 없어서 해당 마일스톤 알림은 영구 누락됩니다.

PENDING 상태와 생성 시각을 기준으로 재처리하는 배치 잡을 추가하면 좋습니다. process는 이미 상태 검사로 멱등성을 확보하므로 배치 재실행에 안전합니다.

🤖 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
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.java`
around lines 35 - 40, PostLikeMilestoneNotificationProcessor의 process에서 예외로
PENDING에 남은 이력을 복구할 재처리 배치를 추가하세요. PENDING 상태이면서 생성 시각이 기준 시간을 지난
PostLikeMilestoneAchievement를 조회해 각 achievementId로 process를 호출하고, 기존 process의 상태
검사와 멱등성을 그대로 활용하세요.
app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessorTest.java (1)

99-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

저장되는 알림 본문도 검증하면 좋겠습니다.

현재 save(any(Notification.class))만 검증하므로 서비스 알림의 제목·본문 회귀를 잡지 못합니다. 푸시 문구는 이벤트로 검증되지만 서비스 알림 문구는 검증 공백입니다.

ArgumentCaptor로 저장된 Notification을 포착해 문구를 확인해 주세요.

♻️ 제안 변경
 			InOrder inOrder = inOrder(notificationWriter, achievementWriter, eventPublisher);
-			inOrder.verify(notificationWriter).save(any(Notification.class));
+			ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
+			inOrder.verify(notificationWriter).save(notificationCaptor.capture());
+			assertThat(notificationCaptor.getValue().getTitle())
+				.isEqualTo(String.format("게시물이 좋아요 %d개를 달성했습니다!", milestoneCount));
 			inOrder.verify(notificationWriter).saveLog(postWriter, notification);

org.mockito.ArgumentCaptor 임포트가 필요합니다. Notification의 실제 접근자 이름에 맞춰 조정해 주세요.

As per path instructions: "테스트 케이스가 충분한지 확인합니다 (성공/실패/경계 케이스)."

🤖 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
`@app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessorTest.java`
around lines 99 - 104, Update the notification verification in
PostLikeMilestoneNotificationProcessorTest to capture the Notification passed to
notificationWriter.save using ArgumentCaptor, then assert its title and body
match the expected service-notification text. Preserve the existing InOrder
verification and event assertions, and add coverage for the relevant
success/failure/boundary cases only if needed to validate the message behavior.

Source: Path instructions

🤖 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
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java`:
- Around line 31-36: PostLikeMilestoneAchievement 엔티티 변경에 대응하는 Flyway 마이그레이션을
추가하세요. tb_post_like_milestone_achievement 테이블과 post_id, trigger_user_id,
notification_id 외래 키, uk_post_like_milestone_achievement_post_milestone 유니크 제약,
idx_post_like_milestone_achievement_trigger_user 인덱스를 생성하고, PR에 db-change 라벨을
지정하세요.

In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java`:
- Around line 17-20: Remove the class-level `@Transactional` from
PostLikeMilestoneAchievementWriter and keep this writer focused on persistence
state changes. Move the transaction boundary to the appropriate Service method
or explicit use-case entry point that coordinates the milestone achievement
flow, using method-level `@Transactional` there.
- Around line 24-36: Make savePendingIfAbsent atomically handle duplicate
post_id and milestone_count inserts by catching DataIntegrityViolationException,
returning Optional.empty() only when it originates from the
PostLikeMilestoneAchievement unique constraint, and rethrowing all other
integrity violations; preserve successful saves so
notificationProcessor::process can run. Add the required Flyway migration for
this unique constraint. In
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java:24-36
update PostLikeMilestoneAchievementWriter; in
app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java:36-65
add a `@DataJpaTest` parallel-save case proving only one call succeeds for the
same post and milestone.

In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListener.java`:
- Around line 24-33: Update PostLikeMilestonePushListener.handle to catch
push-delivery failures from NotificationPushSender and persist or enqueue a
durable compensation/retry task for the associated PostLikeMilestoneAchievement,
allowing failed notifications to be resent. Preserve the existing recipient
lookup and successful send flow, and ensure compensation covers failures
propagated from PostLikeMilestoneNotificationProcessor.

---

Nitpick comments:
In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.java`:
- Around line 35-40: PostLikeMilestoneNotificationProcessor의 process에서 예외로
PENDING에 남은 이력을 복구할 재처리 배치를 추가하세요. PENDING 상태이면서 생성 시각이 기준 시간을 지난
PostLikeMilestoneAchievement를 조회해 각 achievementId로 process를 호출하고, 기존 process의 상태
검사와 멱등성을 그대로 활용하세요.

In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListener.java`:
- Around line 21-26: Update PostLikeMilestoneReachedListener.handle to catch
exceptions from the asynchronous
achievementRecorder.record/notificationProcessor flow and log them through an
SLF4J logger. Include the history ID and post ID in the failure log for
traceability, exclude likerId, and preserve the existing processing behavior.

In
`@app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessorTest.java`:
- Around line 99-104: Update the notification verification in
PostLikeMilestoneNotificationProcessorTest to capture the Notification passed to
notificationWriter.save using ArgumentCaptor, then assert its title and body
match the expected service-notification text. Preserve the existing InOrder
verification and event assertions, and add coverage for the relevant
success/failure/boundary cases only if needed to validate the message behavior.

In
`@app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListenerTest.java`:
- Around line 54-64: Update handle_shouldRunAfterCommit in
PostLikeMilestonePushListenerTest to also retrieve and assert the listener
method’s `@Async` annotation value is "asyncExecutor" and its `@Transactional`
annotation propagation is Propagation.REQUIRES_NEW, while preserving the
existing AFTER_COMMIT assertion.
🪄 Autofix

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 Plus

Run ID: 3acaaac0-cf6d-4719-8414-c4c5d3fdf4e7

📥 Commits

Reviewing files that changed from the base of the PR and between e861825 and 543b5aa.

📒 Files selected for processing (28)
  • app-main/src/main/java/net/causw/app/main/domain/community/post/service/LikePostService.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/enums/PostLikeMilestoneAchievementStatus.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/enums/PostLikeMilestoneSuppressionReason.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikeMilestonePushEvent.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikeMilestoneReachedEvent.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikedEvent.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/repository/PostLikeMilestoneAchievementRepository.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementReader.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementRecorder.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessor.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/LikePostNotificationListener.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListener.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListener.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/policy/LikePostNotificationPolicy.java
  • app-main/src/main/java/net/causw/app/main/shared/exception/errorcode/PostLikeMilestoneAchievementErrorCode.java
  • app-main/src/test/java/net/causw/app/main/SchemaValidationTest.java
  • app-main/src/test/java/net/causw/app/main/domain/community/post/service/LikePostServiceTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievementTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementReaderTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementRecorderTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneNotificationProcessorTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/LikePostNotificationListenerTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListenerTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestoneReachedListenerTest.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/policy/LikePostNotificationPolicyTest.java
💤 Files with no reviewable changes (3)
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/LikePostNotificationListener.java
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/event/PostLikedEvent.java
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/listener/LikePostNotificationListenerTest.java

Comment on lines +31 to +36
@Table(name = "tb_post_like_milestone_achievement", uniqueConstraints = {
@UniqueConstraint(name = "uk_post_like_milestone_achievement_post_milestone", columnNames = {"post_id",
"milestone_count"})
}, indexes = {
@Index(name = "idx_post_like_milestone_achievement_trigger_user", columnList = "trigger_user_id")
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== Flyway SQL files =="
fd -t f -e sql . | sort -V

echo "== Achievement schema references =="
fd -t f -e sql . -x rg -n -i -C 3 \
  'post_like_milestone_achievement|uk_post_like_milestone_achievement_post_milestone|idx_post_like_milestone_achievement_trigger_user' {}

echo "== Current PR labels, if available =="
if gh pr view --json labels --jq '.labels[].name' 2>/dev/null; then
  :
else
  echo "PR labels could not be read in this environment."
fi

Repository: CAUCSE/CAUSW_backend

Length of output: 8299


Flyway 스키마 변경을 함께 추가해 주세요.

tb_post_like_milestone_achievement 테이블, 외래 키(post_id, trigger_user_id, notification_id), uk_post_like_milestone_achievement_post_milestone, idx_post_like_milestone_achievement_trigger_user 변경이 엔티티에 추가되어 있으므로, 이 PR에 VYYYYMMDDHHMMSS__create_post_like_milestone_achievement.sql Flyway 마이그레이션과 db-change 라벨을 포함해야 합니다.

🤖 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
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java`
around lines 31 - 36, PostLikeMilestoneAchievement 엔티티 변경에 대응하는 Flyway 마이그레이션을
추가하세요. tb_post_like_milestone_achievement 테이블과 post_id, trigger_user_id,
notification_id 외래 키, uk_post_like_milestone_achievement_post_milestone 유니크 제약,
idx_post_like_milestone_achievement_trigger_user 인덱스를 생성하고, PR에 db-change 라벨을
지정하세요.

Source: Path instructions

Comment on lines +17 to +20
@Component
@RequiredArgsConstructor
@Transactional
public class PostLikeMilestoneAchievementWriter {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

트랜잭션 경계를 Service 메서드로 이동하세요.

PostLikeMilestoneAchievementWriter는 Implementation 계층입니다. Line 19의 클래스 수준 @Transactional은 Writer의 모든 public 메서드를 트랜잭션 진입점으로 만듭니다.

유스케이스를 조합하는 Service 메서드 또는 명시적인 유스케이스 진입점에 트랜잭션 경계를 두세요. Writer는 영속 상태 변경만 수행하게 유지하세요.

As per path instructions, "트랜잭션 경계(@Transactional)가 Service 메서드 단위로 적절하게 설정되어 있는지 확인합니다."

🤖 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
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java`
around lines 17 - 20, Remove the class-level `@Transactional` from
PostLikeMilestoneAchievementWriter and keep this writer focused on persistence
state changes. Move the transaction boundary to the appropriate Service method
or explicit use-case entry point that coordinates the milestone achievement
flow, using method-level `@Transactional` there.

Source: Path instructions

Comment on lines +24 to +36
public Optional<PostLikeMilestoneAchievement> savePendingIfAbsent(
Post post,
User triggerUser,
long milestoneCount) {
if (achievementRepository.existsByPostIdAndMilestoneCount(post.getId(), milestoneCount)) {
return Optional.empty();
}

PostLikeMilestoneAchievement achievement = PostLikeMilestoneAchievement.pending(
post,
triggerUser,
milestoneCount);
return Optional.of(achievementRepository.saveAndFlush(achievement));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline \
  app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java \
  --items all

fd -t f -0 'V.*__.*\.sql$' . |
  xargs -0 -r rg -n -C 4 \
    'PostLikeMilestoneAchievement|post_id|milestone_count|UNIQUE|unique'

rg -n -C 5 \
  'uniqueConstraints|post_id|milestoneCount|milestone_count' \
  app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java

Repository: CAUCSE/CAUSW_backend

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Candidate files"
fd -t f \
  'PostLikeMilestoneAchievementWriter.java|PostLikeMilestoneAchievementTest.java|PostLikeMilestoneAchievement.java|PostLikeMilestoneAchievementRepository.java|.*Notification.*Service.*\.java|.*Application.*Listener.*\.java|.*Event.*\.java' \
  app-main/src/main/java app-main/src/test/java

echo
echo "## Writer"
cat -n app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java

echo
echo "## Entity"
cat -n app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java

echo
echo "## Repository"
fd -t f 'PostLikeMilestoneAchievementRepository.java' app-main/src/main/java -x cat -n {}

echo
echo "## Test"
cat -n app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java

echo
echo "## Migrations mentioning entity/table names"
rg -n -C 4 'PostLikeMilestoneAchievement|post_like_milestone|milestone_count|milestone_count|trigger_user_id|status' app-main/src/main/resources/db/migration app-main/src/main/java/net/causw/app/main/domain/notification || true

echo
echo "## Call sites"
rg -n -C 4 'savePendingIfAbsent|PostLikeMilestoneAchievementWriter|PostLikeMilestoneAchievement' app-main/src/main/java app-main/src/test/java

Repository: CAUCSE/CAUSW_backend

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## PostLikeMilestoneAchievement call sites"
rg -n -C 5 'savePendingIfAbsent|PostLikeMilestoneAchievementWriter|PostLikeMilestoneReachedEvent|PostLikeMilestoneReachedListener|PostLikeMilestoneNotificationProcessor|transactionalEventListener' \
  app-main/src/main/java/net/causw/app/main/domain/notification app-main/src/test/java

echo
echo "## Writer usages in repositories/services via direct grep across app-main"
rg -n -C 4 'PostLikeMilestoneAchievementWriter|savePendingIfAbsent' app-main/src/main/java app-main/src/test/java || true

Repository: CAUCSE/CAUSW_backend

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Conflict handling symbols"
rg -n -C 3 'DataIntegrityViolationException|isPostMilestoneUniqueConstraintViolation|PostLikeMilestoneAchievementErrorCode|PostLikeMilestoneAchievement' \
  app-main/src/main/java/net/causw/app/main/domain/notification | head -n 220

echo
echo "## Focused constraint check"
python3 - <<'PY'
from pathlib import Path
target = "tb_post_like_milestone_achievement"
for path in Path("app-main/src/main/resources/db/migration").glob("*.sql"):
    txt = path.read_text(errors="ignore").lower()
    if target in txt:
        lines = txt.splitlines()
        for i, line in enumerate(lines, 1):
            if target in line or "uniqu" in line or "constraint" in line:
                print(f"{path}:{i}:{line.strip()}")
PY

Repository: CAUCSE/CAUSW_backend

Length of output: 32447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## ErrorCodes related to milestone achievement"
fd -t f 'PostLikeMilestoneAchievementErrorCode.java|.*ErrorCode.*java' app-main/src/main/java/net/causw/app/main/domain -x sh -c 'echo "--- $1"; rg -n -C 3 "POST_LIKE_MILESTONE_ACHIEVEMENT|milestone|Unique|UNIQUE|unique" "$1" || true' sh {}

echo
echo "## Base exception / runtime exception handling"
rg -n -C 3 'BaseException|BaseRunTimeV2Exception|RuntimeException|`@ExceptionHandler`|DataIntegrityViolationException|Postlike' app-main/src/main/java/net/causw/app/main/shared app-main/src/main/java/net/causw/app/main/domain/notification | head -n 260

Repository: CAUCSE/CAUSW_backend

Length of output: 27673


마일스톤 중복 방지 로직을 원자적으로 처리하세요.

savePendingIfAbsent()existsByPostIdAndMilestoneCount()saveAndFlush()가 별도 작업이라, 같은 게시글과 마일스톤 이벤트가 동시에 처리되면 두 요청 모두 저장합니다. savePendingIfAbsent()의 충돌 상황을 Optional.empty()로 처리하고, 충돌이 없으면 notificationProcessor::process가 실행될 수 있습니다.

현재 PostLikeMilestoneAchievement 엔티티는 (post_id, milestone_count)에 JPA UniqueConstraint가 있지만, 이 제약에 대한 Flyway 생성迁移와 충돌 오류를 처리하는 예외 처리는 없습니다. DataIntegrityViolationException을 필터해서 해당 제약 only Optional.empty() 처리하고, 다른 무결성 오류는 그대로 전파하세요.

또한 PostLikeMilestoneAchievementWriterTest는 Mockito 단위 케이스만覆蓋하므로, post_id + milestone_count 동시 저장 시 DB가 중복을 막고 한 호출만 처리되는지 확인하는 @DataJpaTest 병렬 케이스를 추가하세요.

📍 Affects 2 files
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java#L24-L36 (this comment)
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java#L36-L65
🤖 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
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java`
around lines 24 - 36, Make savePendingIfAbsent atomically handle duplicate
post_id and milestone_count inserts by catching DataIntegrityViolationException,
returning Optional.empty() only when it originates from the
PostLikeMilestoneAchievement unique constraint, and rethrowing all other
integrity violations; preserve successful saves so
notificationProcessor::process can run. Add the required Flyway migration for
this unique constraint. In
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java:24-36
update PostLikeMilestoneAchievementWriter; in
app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java:36-65
add a `@DataJpaTest` parallel-save case proving only one call succeeds for the
same post and milestone.

Source: Path instructions

Comment on lines +24 to +33
@Async("asyncExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handle(PostLikeMilestonePushEvent event) {
User recipient = userReader.findUserById(event.recipientUserId());
notificationPushSender.sendToUser(
recipient,
event.pushTitle(),
event.pushBody(),
event.pushData());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline \
  app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/NotificationPushSender.java \
  --items all

rg -n -C 8 \
  '\bsend(ToUser)?\s*\(|timeout|retry|Retry|catch\s*\(|throw\s+' \
  app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/NotificationPushSender.java

rg -n -C 6 \
  'markNotificationCreated|PostLikeMilestonePushEvent|NOTIFICATION_CREATED' \
  app-main/src/main/java

Repository: CAUCSE/CAUSW_backend

Length of output: 20617


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== PushNotificationSender candidates =="
fd -i 'PushNotificationSender' app-main/src/main/java || true

echo
echo "== NotificationPushSender full =="
cat -n app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/NotificationPushSender.java

echo
echo "== PushNotificationSender implementations/usages =="
rg -n -C 10 'interface\s+PushNotificationSender|class\s+\w+\s+implements\s+PushNotificationSender|`@Bean`|PushNotificationSender|\.send\(' app-main/src/main/java/net/causw/app/main/shared app-main/src/main/java/net/causw/app/main/domain || true

echo
echo "== Async/timeout/thread config references =="
rg -n -C 4 '`@EnableAsync`|AsyncConfigurer|TaskExecutor|asyncExecutor|`@Timeout`|Timeout|setWaitTimeAfterSubmit|maxPoolSize|queueCapacity' app-main/src/main/java | head -n 200 || true

Repository: CAUCSE/CAUSW_backend

Length of output: 50376


푸시 실패 보상 처리를 추가하세요.

PostLikeMilestoneAchievement는 푸시 이벤트 발행 전에 NOTIFICATION_CREATED 상태로 저장됩니다. NotificationPushSenderFirebaseMessagingException을 토큰 제거와 로그로 막지만, 일반 예외는 로그만 남기고 재시도나 보상을 제공하지 않습니다. PostLikeMilestoneNotificationProcessor에서 푸시 발행 실패가 발생할 수 있으므로 실패 알림을 내구성 있는 작업으로 재보낼 수 있도록 처리하세요.

🤖 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
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/listener/PostLikeMilestonePushListener.java`
around lines 24 - 33, Update PostLikeMilestonePushListener.handle to catch
push-delivery failures from NotificationPushSender and persist or enqueue a
durable compensation/retry task for the associated PostLikeMilestoneAchievement,
allowing failed notifications to be resent. Preserve the existing recipient
lookup and successful send flow, and ensure compensation covers failures
propagated from PostLikeMilestoneNotificationProcessor.

Source: Path instructions

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants