Skip to content

Comments

Discord Webhook 알림 연동 (#161)#164

Merged
ryuwldnjs merged 3 commits intomainfrom
161-feature-discord-webhook-알림-연동
Feb 17, 2026

Hidden character warning

The head ref may contain hidden characters: "161-feature-discord-webhook-\uc54c\ub9bc-\uc5f0\ub3d9"
Merged

Discord Webhook 알림 연동 (#161)#164
ryuwldnjs merged 3 commits intomainfrom
161-feature-discord-webhook-알림-연동

Conversation

@ryuwldnjs
Copy link
Member

@ryuwldnjs ryuwldnjs commented Feb 17, 2026

  • infrastructure/discord/ 패키지 신설 (DiscordNotifier 인터페이스 + prod 구현체)
  • 스케줄러 배치 결과 Discord 알림 (성공/실패 색상 구분, 소요시간 포함)
  • 회원가입, 핸들등록 이벤트 알림
  • BatchResult record 도입으로 서비스 반환 타입 변경 (void → BatchResult)
  • 에러 알림은 Sentry가 담당하므로 제외

관련 이슈

변경 내용

  • 환경 분리: @Profile("prod") 실제 Webhook / @Profile("!prod") 로그 출력
  • 채널 분리: scheduler(배치 결과) + event(비즈니스 이벤트) 2개
  • 실패 격리: Discord 알림 실패가 비즈니스 로직에 영향 없음

변경 유형

  • [FEATURE] 기능 구현
  • [BUG] 버그 수정
  • [REFACTOR] 리팩토링
  • [CHORE] 설정/의존성
  • [DOCS] 문서

테스트

  • 테스트 완료

스크린샷 (UI 변경 시)

참고사항

  • prod 배포 시 환경변수 필요: DISCORD_WEBHOOK_SCHEDULER, DISCORD_WEBHOOK_EVENT
  • 환경변수 미설정 시 알림만 건너뜀 (서비스 영향 없음)

Summary by CodeRabbit

  • New Features

    • Discord 웹훅 통합 추가 — 신규 회원 등록과 핸들 검증 완료 시 알림 전송
    • 예약된 작업(메일 발송·추천 생성)의 배치 결과 및 오류를 Discord로 보고
    • 로컬/테스트용 가짜 Discord 알림기 제공(프로덕션과 분리)
  • Chores

    • Discord 웹훅 URL 환경 변수로 설정 가능하도록 구성 항목 추가
    • 배치 결과를 반환하는 결과 객체 도입으로 상태 집계 개선

- infrastructure/discord/ 패키지 신설 (DiscordNotifier 인터페이스 + prod 구현체)
- 스케줄러 배치 결과 Discord 알림 (성공/실패 구분)
- 회원가입, 핸들등록 이벤트 알림
- BatchResult record 도입으로 서비스 반환 타입 변경
@ryuwldnjs ryuwldnjs linked an issue Feb 17, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'path_filters'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

이 PR은 Discord 웹훅 기반 알림 인프라를 추가하고, 스케줄러·이메일 배치 및 회원 생성 흐름에 Discord 알림을 통합하며, 일부 배치 메서드들의 반환 타입을 BatchResult로 변경합니다.

Changes

Cohort / File(s) Summary
Discord 인프라 추가
src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordMessage.java, src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordNotifier.java, src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordProperties.java, src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordWebhookNotifier.java, src/main/java/com/ryu/studyhelper/infrastructure/discord/FakeDiscordNotifier.java
Discord 메시지 포맷 레코드(DiscordMessage), 알림 추상화 인터페이스(DiscordNotifier), 설정 바인딩 레코드(DiscordProperties), 프로덕션 웹훅 전송 구현(DiscordWebhookNotifier) 및 프로파일별 페이크 구현(FakeDiscordNotifier) 추가.
회원 관련 통합
src/main/java/com/ryu/studyhelper/auth/GoogleOAuth2UserService.java, src/main/java/com/ryu/studyhelper/member/MemberService.java
신규 회원 생성 및 핸들 검증 시 Discord 이벤트 전송 로직 추가(이메일 마스킹 포함). DiscordNotifier 의존성 주입.
스케줄러·배치 알림 통합
src/main/java/com/ryu/studyhelper/recommendation/scheduler/EmailSendScheduler.java, src/main/java/com/ryu/studyhelper/recommendation/scheduler/ProblemRecommendationScheduler.java
스케줄러 실행 결과를 BatchResult로 캡처하고 성공/실패에 따라 Discord에 알림 전송하도록 흐름을 재구성. 공통 notifyDiscord 헬퍼 추가.
배치 결과 타입 변경
src/main/java/com/ryu/studyhelper/recommendation/dto/internal/BatchResult.java, src/main/java/com/ryu/studyhelper/recommendation/service/RecommendationEmailService.java, src/main/java/com/ryu/studyhelper/recommendation/service/ScheduledRecommendationService.java
BatchResult 레코드 추가 및 sendAll() / prepareDailyRecommendations() 등 반환 타입을 voidBatchResult로 변경하여 결과 집계 반환.
설정 추가
src/main/resources/application.yml
discord.webhooks 설정 항목(scheduler, event)을 환경변수 바인딩으로 추가.

Sequence Diagram

sequenceDiagram
    actor User
    participant GoogleOAuth2UserService
    participant DiscordNotifier as DiscordNotifier
    participant DiscordWebhookNotifier
    participant DiscordAPI as Discord API

    User->>GoogleOAuth2UserService: OAuth2 로그인 (신규 사용자)
    activate GoogleOAuth2UserService
    GoogleOAuth2UserService->>GoogleOAuth2UserService: 새 멤버 생성 및 이메일 마스킹
    GoogleOAuth2UserService->>DiscordNotifier: sendEvent(DiscordMessage)
    deactivate GoogleOAuth2UserService
    activate DiscordWebhookNotifier
    DiscordWebhookNotifier->>DiscordWebhookNotifier: webhook URL 검증
    DiscordWebhookNotifier->>DiscordAPI: POST /webhooks (application/json)
    DiscordAPI-->>DiscordWebhookNotifier: 2xx/204 응답
    deactivate DiscordWebhookNotifier
Loading
sequenceDiagram
    participant Scheduler
    participant ScheduledRecommendationService
    participant RecommendationEmailService
    participant DiscordNotifier as DiscordNotifier
    participant DiscordWebhookNotifier
    participant DiscordAPI as Discord API

    Scheduler->>ScheduledRecommendationService: prepareDailyRecommendations()
    activate ScheduledRecommendationService
    ScheduledRecommendationService->>RecommendationEmailService: sendAll()
    activate RecommendationEmailService
    RecommendationEmailService-->>ScheduledRecommendationService: BatchResult
    deactivate RecommendationEmailService
    ScheduledRecommendationService->>ScheduledRecommendationService: 처리 결과 집계 (BatchResult / Exception)
    ScheduledRecommendationService-->>DiscordNotifier: sendScheduler(DiscordMessage)  (성공 또는 에러 기반)
    deactivate ScheduledRecommendationService
    activate DiscordWebhookNotifier
    DiscordWebhookNotifier->>DiscordAPI: POST /webhooks (scheduler)
    DiscordAPI-->>DiscordWebhookNotifier: 2xx/204 응답
    deactivate DiscordWebhookNotifier
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 웅성웅성 디스코드 별빛 아래
새 멤버 오면 소식 가볍게 전하고
배치가 끝나면 당근 춤추며 알리네
로그 대신 종소리로 운영을 깨우고
토끼가 기쁘게 메시지 전해요 ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 제목 'Discord Webhook 알림 연동 (#161)'은 변경 사항의 핵심인 Discord Webhook 알림 통합을 명확하게 요약합니다.
Linked Issues check ✅ Passed PR 변경 사항이 이슈 #161의 주요 요구사항을 충족합니다: Discord Webhook 인터페이스 구현, 배치 결과 알림(성공/실패 색상 구분), 비즈니스 이벤트 알림, 프로필별 환경 분리, 채널 분리(scheduler/event).
Out of Scope Changes check ✅ Passed 모든 변경 사항이 이슈 #161 범위 내입니다. 추가된 파일들은 모두 Discord 알림 기능 구현과 관련된 것이며, BatchResult 반환 타입 변경도 배치 결과 추적을 위한 필수 변경입니다.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 161-feature-discord-webhook-알림-연동

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.

@github-actions
Copy link

github-actions bot commented Feb 17, 2026

🧪 테스트 결과

145 tests   143 ✅  2s ⏱️
 30 suites    2 💤
 30 files      0 ❌

Results for commit 1294024.

♻️ This comment has been updated with latest results.

Copy link

@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 (7)
src/main/java/com/ryu/studyhelper/auth/GoogleOAuth2UserService.java (1)

70-76: maskEmail에서 @가 없는 입력에 대한 방어 처리 부재.

email.indexOf('@')-1을 반환하면 email.substring(-1)에서 StringIndexOutOfBoundsException이 발생합니다. Google OAuth에서 유효한 이메일이 오겠지만, 방어적 코딩을 권장합니다.

🛡️ 방어 코드 제안
 private String maskEmail(String email) {
+    if (email == null || !email.contains("@")) {
+        return "***";
+    }
     int atIndex = email.indexOf('@');
     if (atIndex <= 3) {
         return email.charAt(0) + "***" + email.substring(atIndex);
     }
     return email.substring(0, 3) + "***" + email.substring(atIndex);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/ryu/studyhelper/auth/GoogleOAuth2UserService.java` around
lines 70 - 76, maskEmail currently assumes an '@' exists and calls
substring(atIndex) which throws StringIndexOutOfBoundsException when
indexOf('@') returns -1; update maskEmail to defensively handle null/empty
inputs and the case where atIndex == -1 by first validating email (null/empty)
and checking if atIndex < 0, then return a safe fallback (e.g., the original
email or a partially masked version like first 3 chars + "***") instead of
calling substring(-1); keep the existing behavior for normal emails (use
email.charAt(0)/substring(0,3) and substring(atIndex) when atIndex >= 0).
src/main/java/com/ryu/studyhelper/recommendation/dto/internal/BatchResult.java (1)

1-6: 깔끔한 record 정의입니다.

배치 결과를 캡슐화하는 간결한 구현입니다.

참고: ScheduledRecommendationService에서 이미 추천이 존재하는 팀은 continue로 스킵되므로 totalCount != successCount + failCount인 경우가 발생할 수 있습니다. 이는 의도된 동작이라면 문제없지만, skippedCount 필드를 추가하면 알림 메시지에서 더 정확한 정보를 전달할 수 있습니다.

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

In
`@src/main/java/com/ryu/studyhelper/recommendation/dto/internal/BatchResult.java`
around lines 1 - 6, BatchResult should include a skippedCount to represent teams
that were intentionally skipped so totals reconcile; update the record
definition (BatchResult) to add an int skippedCount field and ensure all places
that construct it (notably ScheduledRecommendationService where you currently
continue for already-existing recommendations) pass skippedCount (or compute
skippedCount = totalCount - successCount - failCount when appropriate) and
update any notification/summary logic that reads total/success/fail to also
include skippedCount in messages.
src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordWebhookNotifier.java (1)

5-5: 사용되지 않는 @Primary import가 있습니다.

🧹 미사용 import 제거
-import org.springframework.context.annotation.Primary;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordWebhookNotifier.java`
at line 5, Remove the unused import
org.springframework.context.annotation.Primary from the DiscordWebhookNotifier
class (import line shown in the diff) — delete that import and run
import-organize/formatting so there are no unused imports remaining in
src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordWebhookNotifier.java.
src/main/java/com/ryu/studyhelper/recommendation/scheduler/EmailSendScheduler.java (1)

34-40: ProblemRecommendationScheduler와 동일하게 소요시간 계산 불일치 문제가 있습니다.

Line 34, 37의 로그 소요시간과 Line 40의 notifyDiscord 전달 소요시간이 서로 다른 System.currentTimeMillis() 호출로 계산됩니다. ProblemRecommendationScheduler에 제안한 것과 동일하게 소요시간을 한 번만 계산하여 일관되게 사용하세요.

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

In
`@src/main/java/com/ryu/studyhelper/recommendation/scheduler/EmailSendScheduler.java`
around lines 34 - 40, Compute the duration once after the try/catch using the
existing startTime (e.g., long duration = System.currentTimeMillis() -
startTime) and reuse that single duration variable in the log calls and in the
notifyDiscord(result, failure, duration) call so all messages use the same
elapsed time; update EmailSendScheduler to replace the multiple
System.currentTimeMillis() calls with this single duration variable.
src/main/java/com/ryu/studyhelper/recommendation/scheduler/ProblemRecommendationScheduler.java (2)

34-40: 소요시간이 두 번 계산되어 Discord 알림에 전달되는 값이 로그 값과 다릅니다.

Line 34, 37에서 로그에 기록하는 소요시간과 Line 40에서 notifyDiscord에 전달하는 소요시간이 별도의 System.currentTimeMillis() 호출로 계산됩니다. Discord 알림에는 로그보다 약간 더 큰 값이 전달됩니다.

소요시간을 한 번만 계산하여 일관되게 사용하세요.

♻️ 소요시간 일관성 개선
         try {
             result = scheduledRecommendationService.prepareDailyRecommendations();
-            log.info("=== 문제 추천 배치 작업 완료 === (소요시간: {}ms)", System.currentTimeMillis() - startTime);
         } catch (Exception e) {
             failure = e;
-            log.error("=== 문제 추천 배치 작업 실패 === (소요시간: {}ms)", System.currentTimeMillis() - startTime, e);
         }
 
-        notifyDiscord(result, failure, System.currentTimeMillis() - startTime);
+        long elapsed = System.currentTimeMillis() - startTime;
+
+        if (failure != null) {
+            log.error("=== 문제 추천 배치 작업 실패 === (소요시간: {}ms)", elapsed, failure);
+        } else {
+            log.info("=== 문제 추천 배치 작업 완료 === (소요시간: {}ms)", elapsed);
+        }
+
+        notifyDiscord(result, failure, elapsed);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/ryu/studyhelper/recommendation/scheduler/ProblemRecommendationScheduler.java`
around lines 34 - 40, Compute the elapsed time once and reuse it instead of
calling System.currentTimeMillis() twice: after the try/catch calculate long
elapsed = System.currentTimeMillis() - startTime and replace the two inline
System.currentTimeMillis() - startTime usages in log.info/log.error and the
notifyDiscord(result, failure, ...) call with that elapsed variable so the
logged message (in the log.info/log.error blocks) and the notifyDiscord(...)
invocation use the identical duration; update references around notifyDiscord
and the existing startTime variable accordingly.

43-55: notifyDiscord 메서드가 EmailSendScheduler와 완전히 동일하게 중복됩니다.

두 스케줄러의 notifyDiscord 로직이 제목 문자열만 다르고 구조가 동일합니다. 공통 유틸리티나 헬퍼로 추출하면 유지보수성이 향상됩니다. 비동기 이벤트 처리로 분리 예정이라면 그때 함께 정리해도 무방합니다.

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

In
`@src/main/java/com/ryu/studyhelper/recommendation/scheduler/ProblemRecommendationScheduler.java`
around lines 43 - 55, notifyDiscord in ProblemRecommendationScheduler duplicates
EmailSendScheduler; extract the shared logic into a single helper (e.g.,
SchedulerNotifier.notifyDiscord or NotificationHelper.notifyDiscord) that
accepts the Discord notifier (discordNotifier), BatchResult result, Exception
failure, long elapsed and two title strings (successTitle and failureTitle) or a
single failure/success title decision inside the helper; update
ProblemRecommendationScheduler.notifyDiscord and
EmailSendScheduler.notifyDiscord to call that helper, preserving use of
DiscordMessage.error and DiscordMessage.batchResult and the try/catch with
log.warn for notification exceptions.
src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordMessage.java (1)

54-60: event() 메서드에서 keyValues 길이가 홀수일 경우 마지막 값이 무시됩니다.

keyValues가 이름-값 쌍으로 전달되어야 하지만, 호출자가 실수로 홀수 개를 전달하면 마지막 항목이 조용히 무시됩니다. 방어적으로 검증을 추가하면 디버깅이 쉬워집니다.

🛡️ 홀수 길이 방어 코드 제안
     public static DiscordMessage event(String title, String... keyValues) {
+        if (keyValues.length % 2 != 0) {
+            throw new IllegalArgumentException("keyValues는 이름-값 쌍이어야 합니다 (짝수 개)");
+        }
         List<Field> fields = new ArrayList<>();
         for (int i = 0; i + 1 < keyValues.length; i += 2) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordMessage.java`
around lines 54 - 60, The event(String title, String... keyValues) method
silently drops a trailing value when keyValues has odd length; add defensive
validation at the start of event(...) to check keyValues != null and that
keyValues.length is even, and if not throw an IllegalArgumentException (or
return a clear error) indicating that keyValues must be provided as name/value
pairs; keep the rest of the method (building List<Field> and calling
create(title, COLOR_INFO, fields)) unchanged so callers like Field and create
are used as before.
🤖 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/ryu/studyhelper/infrastructure/discord/DiscordNotifier.java`:
- Around line 3-6: Javadoc for DiscordNotifier incorrectly mentions an "에러"
channel that doesn't exist; update the class/interface comment in
DiscordNotifier to remove "에러" (or replace with correct channels) so it only
lists the actual methods provided (e.g., 스케줄러, 이벤트), and ensure the description
clearly states that error reporting is handled by Sentry rather than a sendError
method.

In
`@src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordWebhookNotifier.java`:
- Around line 23-26: The RestClient in the DiscordWebhookNotifier constructor is
created without timeouts (restClient = RestClient.create()), which can block the
scheduler; replace this with RestClient.builder() and supply a configured
requestFactory that sets short connection and read timeouts (eg. connect ~2s,
read ~5s) before calling build(); update the DiscordWebhookNotifier constructor
to use the builder approach so all outgoing Discord calls use the
timeout-configured RestClient.

In `@src/main/java/com/ryu/studyhelper/member/MemberService.java`:
- Around line 86-89: verifySolvedAcHandle is a `@Transactional` method and calling
discordNotifier.sendEvent(...) can throw and roll back
member.changeHandle(handle); to fix, prevent notifier exceptions from escaping
the transaction by wrapping the discordNotifier.sendEvent call in a try-catch
that logs failures (without rethrowing) or modify the notifier implementation to
swallow/handle its own exceptions; locate discordNotifier.sendEvent(...) in
MemberService (and the call site where member.changeHandle(handle) is performed)
and ensure sendEvent failures are caught and handled so the transaction commits
even if the Discord notification fails.

---

Nitpick comments:
In `@src/main/java/com/ryu/studyhelper/auth/GoogleOAuth2UserService.java`:
- Around line 70-76: maskEmail currently assumes an '@' exists and calls
substring(atIndex) which throws StringIndexOutOfBoundsException when
indexOf('@') returns -1; update maskEmail to defensively handle null/empty
inputs and the case where atIndex == -1 by first validating email (null/empty)
and checking if atIndex < 0, then return a safe fallback (e.g., the original
email or a partially masked version like first 3 chars + "***") instead of
calling substring(-1); keep the existing behavior for normal emails (use
email.charAt(0)/substring(0,3) and substring(atIndex) when atIndex >= 0).

In
`@src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordMessage.java`:
- Around line 54-60: The event(String title, String... keyValues) method
silently drops a trailing value when keyValues has odd length; add defensive
validation at the start of event(...) to check keyValues != null and that
keyValues.length is even, and if not throw an IllegalArgumentException (or
return a clear error) indicating that keyValues must be provided as name/value
pairs; keep the rest of the method (building List<Field> and calling
create(title, COLOR_INFO, fields)) unchanged so callers like Field and create
are used as before.

In
`@src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordWebhookNotifier.java`:
- Line 5: Remove the unused import
org.springframework.context.annotation.Primary from the DiscordWebhookNotifier
class (import line shown in the diff) — delete that import and run
import-organize/formatting so there are no unused imports remaining in
src/main/java/com/ryu/studyhelper/infrastructure/discord/DiscordWebhookNotifier.java.

In
`@src/main/java/com/ryu/studyhelper/recommendation/dto/internal/BatchResult.java`:
- Around line 1-6: BatchResult should include a skippedCount to represent teams
that were intentionally skipped so totals reconcile; update the record
definition (BatchResult) to add an int skippedCount field and ensure all places
that construct it (notably ScheduledRecommendationService where you currently
continue for already-existing recommendations) pass skippedCount (or compute
skippedCount = totalCount - successCount - failCount when appropriate) and
update any notification/summary logic that reads total/success/fail to also
include skippedCount in messages.

In
`@src/main/java/com/ryu/studyhelper/recommendation/scheduler/EmailSendScheduler.java`:
- Around line 34-40: Compute the duration once after the try/catch using the
existing startTime (e.g., long duration = System.currentTimeMillis() -
startTime) and reuse that single duration variable in the log calls and in the
notifyDiscord(result, failure, duration) call so all messages use the same
elapsed time; update EmailSendScheduler to replace the multiple
System.currentTimeMillis() calls with this single duration variable.

In
`@src/main/java/com/ryu/studyhelper/recommendation/scheduler/ProblemRecommendationScheduler.java`:
- Around line 34-40: Compute the elapsed time once and reuse it instead of
calling System.currentTimeMillis() twice: after the try/catch calculate long
elapsed = System.currentTimeMillis() - startTime and replace the two inline
System.currentTimeMillis() - startTime usages in log.info/log.error and the
notifyDiscord(result, failure, ...) call with that elapsed variable so the
logged message (in the log.info/log.error blocks) and the notifyDiscord(...)
invocation use the identical duration; update references around notifyDiscord
and the existing startTime variable accordingly.
- Around line 43-55: notifyDiscord in ProblemRecommendationScheduler duplicates
EmailSendScheduler; extract the shared logic into a single helper (e.g.,
SchedulerNotifier.notifyDiscord or NotificationHelper.notifyDiscord) that
accepts the Discord notifier (discordNotifier), BatchResult result, Exception
failure, long elapsed and two title strings (successTitle and failureTitle) or a
single failure/success title decision inside the helper; update
ProblemRecommendationScheduler.notifyDiscord and
EmailSendScheduler.notifyDiscord to call that helper, preserving use of
DiscordMessage.error and DiscordMessage.batchResult and the try/catch with
log.warn for notification exceptions.

Copy link

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

🧹 Nitpick comments (1)
src/main/java/com/ryu/studyhelper/infrastructure/discord/FakeDiscordNotifier.java (1)

26-31: title()null일 경우 로그에 "null"이 출력될 수 있습니다.

embeds 리스트의 존재 여부는 확인하고 있지만, 첫 번째 embed의 title()null인 경우는 처리되지 않습니다. 로그 전용이라 실질적 영향은 없지만, 일관성을 위해 방어 처리를 고려해볼 수 있습니다.

🔧 제안: null-safe title 추출
     private String extractTitle(DiscordMessage message) {
         if (message.embeds() == null || message.embeds().isEmpty()) {
             return "(제목 없음)";
         }
-        return message.embeds().get(0).title();
+        String title = message.embeds().get(0).title();
+        return title != null ? title : "(제목 없음)";
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/ryu/studyhelper/infrastructure/discord/FakeDiscordNotifier.java`
around lines 26 - 31, The extractTitle method currently checks for missing
embeds but not for a null first-embed title; update
FakeDiscordNotifier.extractTitle to null-safely handle the first embed's title
by checking message.embeds().get(0).title() for null and returning a fallback
like "(제목 없음)" (or an empty string) when title() is null, ensuring logs never
print "null" while keeping existing embed-existence checks intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/main/java/com/ryu/studyhelper/infrastructure/discord/FakeDiscordNotifier.java`:
- Around line 26-31: The extractTitle method currently checks for missing embeds
but not for a null first-embed title; update FakeDiscordNotifier.extractTitle to
null-safely handle the first embed's title by checking
message.embeds().get(0).title() for null and returning a fallback like "(제목 없음)"
(or an empty string) when title() is null, ensuring logs never print "null"
while keeping existing embed-existence checks intact.

@ryuwldnjs ryuwldnjs merged commit 47abdd8 into main Feb 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Discord Webhook 알림 연동

1 participant