Skip to content

feat: [NDGL-135] 사용자 컨텐츠 요청 시 디스코드 알림 전송#74

Merged
WooJJam merged 5 commits into
developfrom
feature/NDGL-135-Server-사용자-컨텐츠-요청-시-discord-알림-전송
May 13, 2026

Hidden character warning

The head ref may contain hidden characters: "feature/NDGL-135-Server-\uc0ac\uc6a9\uc790-\ucee8\ud150\uce20-\uc694\uccad-\uc2dc-discord-\uc54c\ub9bc-\uc804\uc1a1"
Merged

feat: [NDGL-135] 사용자 컨텐츠 요청 시 디스코드 알림 전송#74
WooJJam merged 5 commits into
developfrom
feature/NDGL-135-Server-사용자-컨텐츠-요청-시-discord-알림-전송

Conversation

@WooJJam
Copy link
Copy Markdown
Member

@WooJJam WooJJam commented May 12, 2026

🤖 이 PR은 AI를 사용하여 자동 생성되었습니다.

요약

사용자 제안 템플릿 생성 시 Discord로 알림을 발송하도록 이벤트 기반 인프라와 Discord 클라이언트를 추가하고 생성 흐름을 이벤트 발행으로 연결했습니다.

변경 내용

  • 사용자 제안 이벤트(UserSuggestedTemplateCreatedEvent)와 Discord 알림 표현을 추가
  • 사용자 제안 이벤트 퍼블리셔와 비동기 리스너를 추가
  • Discord 알림 전송 계층(노티파이어·웹훅 클라이언트·설정)과 페이로드 제약을 추가
  • 템플릿 생성 흐름을 변경해 templateId 반환을 추가하고 생성 후 이벤트를 발행하도록 연결
  • TextUtils를 추가하고 분산락 결과 타입 처리 및 로깅을 개선

참고 사항

  • Discord webhook이 미설정된 경우 알림을 건너뛰므로 운영환경에 webhook URL을 설정해야 합니다.

Summary by CodeRabbit

새 기능

  • 사용자가 제안한 템플릿에 대한 Discord 알림 기능 추가

리팩토링

  • 분산 잠금 메커니즘 내부 구조 개선

Review Change Stack

@WooJJam WooJJam self-assigned this May 12, 2026
@WooJJam WooJJam added feat 새로운 기능 추가 ai-generated AI로 생성한 PR labels May 12, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Warning

Rate limit exceeded

@WooJJam has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 52 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86a27dd4-166c-4d74-acb4-e65dd3c9d38b

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1e5aa and 62557a7.

📒 Files selected for processing (7)
  • application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateNotification.java
  • application/src/main/resources/application.yml
  • clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordChannel.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotification.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotifier.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordWebhookClient.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/config/DiscordWebhookProperties.java

설명

이 PR은 사용자 제안 템플릿 생성 이벤트를 Discord로 비동기 전송하는 완전한 알림 시스템을 구현합니다.

변경사항

Discord 알림 시스템

레이어 / 파일 설명
텍스트 유틸리티 및 인프라
common/src/main/java/com/yapp/ndgl/common/util/TextUtils.java, clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordPayloadConstraints.java, domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/common/lock/NamedLockRepository.java
문자열 절단, Discord 페이로드 제약사항 상수, 데이터베이스 락 저장소 숫자 처리를 추가합니다.
Discord 클라이언트 인프라
clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotification.java, clients/src/main/java/com/yapp/ndgl/clients/discord/request/DiscordEmbed.java, clients/src/main/java/com/yapp/ndgl/clients/discord/request/DiscordWebhookRequest.java, clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordWebhookClient.java, clients/src/main/java/com/yapp/ndgl/clients/discord/config/DiscordWebhookConfig.java, clients/src/main/java/com/yapp/ndgl/clients/discord/config/DiscordWebhookProperties.java, application/src/main/resources/application.yml
Discord 알림 인터페이스, DTO, HTTP 클라이언트, Spring 설정, 환경 설정 속성을 정의합니다.
Discord 알림 전송기
clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotifier.java
DiscordNotification을 DiscordWebhookRequest로 변환하여 전송합니다.
이벤트 도메인 및 발행
application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateCreatedEvent.java, application/src/main/java/com/yapp/ndgl/application/domains/travel/event/publisher/UserSuggestedTemplateEventPublisher.java
템플릿 생성 이벤트와 발행자를 구현합니다.
이벤트 알림 및 수신
application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateNotification.java, application/src/main/java/com/yapp/ndgl/application/domains/travel/event/listener/UserSuggestedTemplateEventListener.java
이벤트를 Discord 임베드로 변환하고 비동기로 수신하여 전송합니다.
서비스 통합
application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java, application/src/main/java/com/yapp/ndgl/application/domains/travel/facade/UserSuggestedTemplateFacade.java
템플릿 ID를 반환하도록 서비스를 변경하고 Facade에서 이벤트를 발행합니다.
AOP 리팩토링
application/src/main/java/com/yapp/ndgl/application/common/aop/DistributedLockAspect.java
어드바이스 메서드 시그니처를 단순화하고 메서드 시그니처에서 애너테이션을 동적으로 조회합니다.
이벤트 리스너 재구성
application/src/main/java/com/yapp/ndgl/application/domains/travel/event/listener/TravelTemplateEventListener.java
TravelTemplateEventListener를 .listener 서브패키지로 이동합니다.

시퀀스 다이어그램

sequenceDiagram
  participant Client as 사용자
  participant Facade as UserSuggestedTemplateFacade
  participant Service as UserSuggestedTemplateService
  participant Publisher as UserSuggestedTemplateEventPublisher
  participant EventBus as ApplicationEventPublisher
  participant Listener as UserSuggestedTemplateEventListener
  participant Notifier as DiscordNotifier
  participant WebhookClient as DiscordWebhookClient
  participant Discord as Discord Webhook
  Client->>Facade: createUserSuggestedTemplate()
  Facade->>Service: createUserSuggestedTemplate()
  Service-->>Facade: templateId
  Facade->>Publisher: publish(templateId, videoId, uuid, request)
  Publisher->>EventBus: publishEvent(UserSuggestedTemplateCreatedEvent)
  EventBus->>Listener: handleUserSuggestedTemplateCreatedEvent(event)
  Listener->>Notifier: notify(UserSuggestedTemplateNotification)
  Notifier->>WebhookClient: send(DiscordWebhookRequest)
  WebhookClient->>Discord: POST JSON
  Discord-->>WebhookClient: 200 OK
Loading

예상 코드 리뷰 시간

🎯 3 (보통) | ⏱️ ~20분

🐰 Discord의 웹훅으로 날아가는 알림,
비동기 이벤트로 흐르는 흐름이
예쁘게 포장되어 떠나간다네.
분산 락도 다듬고, 패키지도 정렬하고,
이제 템플릿 제안이 Discord에서 반짝인다! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% 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 제목이 변경사항의 핵심을 명확하게 반영하고 있습니다. 사용자 컨텐츠 요청 시 디스코드 알림 전송 기능이 주요 변경사항이며, 제목에서 이를 명확히 드러내고 있습니다.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/NDGL-135-Server-사용자-컨텐츠-요청-시-discord-알림-전송

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

🧹 Nitpick comments (3)
clients/src/main/java/com/yapp/ndgl/clients/discord/config/DiscordWebhookConfig.java (1)

16-17: 💤 Low value

타임아웃 값이 프로덕션 환경에 적합한지 확인 필요

연결 타임아웃 3초와 읽기 타임아웃 5초는 webhook 호출에는 적절해 보이지만, 네트워크 상황에 따라 짧을 수 있습니다. 프로덕션 환경에서 Discord API의 응답 시간을 모니터링하여 타임아웃 값이 충분한지 확인하는 것을 권장합니다.

🤖 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
`@clients/src/main/java/com/yapp/ndgl/clients/discord/config/DiscordWebhookConfig.java`
around lines 16 - 17, The hardcoded CONNECT_TIMEOUT_SECONDS (3) and
READ_TIMEOUT_SECONDS (5) in DiscordWebhookConfig may be too short for
production; make these timeouts configurable and use safer defaults: replace the
constants with configurable properties (e.g., injected via environment,
application properties, or constructor args) and expose names like
discord.webhook.connect-timeout and discord.webhook.read-timeout so operators
can tune them; ensure the code that uses
CONNECT_TIMEOUT_SECONDS/READ_TIMEOUT_SECONDS (the webhook HTTP client creation
logic in DiscordWebhookConfig) reads the injected values (prefer Duration or
seconds parsing) and document the property keys and sensible defaults.
clients/src/main/java/com/yapp/ndgl/clients/discord/request/DiscordWebhookRequest.java (1)

14-16: ⚡ Quick win

embeds는 방어적 복사로 고정해 주세요.

레코드가 외부의 가변 List 참조를 그대로 들고 있으면, 생성 후 payload가 의도치 않게 바뀔 수 있습니다. 생성자에서 List.copyOf(...)로 고정하는 편이 안전합니다.

제안 패치
 public DiscordWebhookRequest {
 	content = TextUtils.truncate(content, DiscordPayloadConstraints.CONTENT_MAX);
+	embeds = embeds == null ? null : List.copyOf(embeds);
 }
🤖 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
`@clients/src/main/java/com/yapp/ndgl/clients/discord/request/DiscordWebhookRequest.java`
around lines 14 - 16, The record DiscordWebhookRequest currently assigns the
external mutable embeds List directly which can allow later mutation of the
payload; in the canonical constructor for DiscordWebhookRequest, defensively
copy the embeds list using List.copyOf(embeds) (or Collections.emptyList() when
null) and assign that to the embeds field, leaving the existing
TextUtils.truncate(content, DiscordPayloadConstraints.CONTENT_MAX) handling
intact so the record holds an unmodifiable snapshot of embeds.
clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotifier.java (1)

18-23: ⚡ Quick win

빈 페이로드(content/embeds 모두 없음) 전송은 사전 차단해 주세요.

현재 계약상 구현체가 빈 페이로드를 반환해도 그대로 전송됩니다. 전송 전에 유효성 가드를 두는 편이 안전합니다.

제안 패치
+import java.util.List;
+
+import org.springframework.util.StringUtils;
 import org.springframework.stereotype.Component;
@@
 	public void notify(final DiscordNotification notification) {
-		DiscordWebhookRequest request = DiscordWebhookRequest.of(
-			notification.createContent(),
-			notification.createEmbeds()
-		);
+		final String content = notification.createContent();
+		final List<com.yapp.ndgl.clients.discord.request.DiscordEmbed> embeds = notification.createEmbeds();
+		if (!StringUtils.hasText(content) && (embeds == null || embeds.isEmpty())) {
+			return;
+		}
+		DiscordWebhookRequest request = DiscordWebhookRequest.of(content, embeds);
 		discordWebhookClient.send(request);
 	}
🤖 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 `@clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotifier.java`
around lines 18 - 23, notify 메서드가 DiscordWebhookRequest를 빈 페이로드(둘 다 없음)로 생성해 그대로
전송하므로, DiscordNotifier.notify에서 DiscordNotification.createContent()와
createEmbeds() 결과를 검사하여 둘 다 비어있거나 null이면 전송하지 않도록 유효성 가드를 추가하세요; 구체적으로 notify
내부에서 content와 embeds를 판정하고(예: null 또는 empty 목록/빈 문자열), 둘 다 비어있을 때
discordWebhookClient.send 호출을 건너뛰고 적절히 로그 또는 반환하도록 변경하세요(대상 심볼:
DiscordNotifier.notify, DiscordNotification.createContent,
DiscordNotification.createEmbeds, DiscordWebhookRequest,
discordWebhookClient.send).
🤖 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
`@application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateNotification.java`:
- Around line 41-42: The notification currently calls
maskUuid(event.suggesterUuid()) in UserSuggestedTemplateNotification
(Field.of("제안자", maskUuid(event.suggesterUuid()), false)) which still exposes a
partial UUID; remove that raw/partially masked identifier from the Discord
payload or replace it with a non-reversible value: compute a server-side salted
one-way hash (e.g., HMAC-SHA256 using a stored salt/secret) of
event.suggesterUuid() and use that hashed token in the Field instead, or simply
omit the Field; update the code in UserSuggestedTemplateNotification to stop
using maskUuid() for external payloads and ensure the chosen salt/secret is
loaded from secure config.

In `@common/src/main/java/com/yapp/ndgl/common/util/TextUtils.java`:
- Around line 16-24: The truncate method in TextUtils currently allows max <= 0
which causes StringIndexOutOfBoundsException; update TextUtils.truncate to
validate the max parameter at the start (e.g., if (max < 1) throw new
IllegalArgumentException("max must be >= 1")) or normalize it (e.g., treat any
value <1 as 1) and document the behavior; ensure the validation occurs before
any substring calls and include a clear error message referencing the max
parameter so callers see the contract.

---

Nitpick comments:
In
`@clients/src/main/java/com/yapp/ndgl/clients/discord/config/DiscordWebhookConfig.java`:
- Around line 16-17: The hardcoded CONNECT_TIMEOUT_SECONDS (3) and
READ_TIMEOUT_SECONDS (5) in DiscordWebhookConfig may be too short for
production; make these timeouts configurable and use safer defaults: replace the
constants with configurable properties (e.g., injected via environment,
application properties, or constructor args) and expose names like
discord.webhook.connect-timeout and discord.webhook.read-timeout so operators
can tune them; ensure the code that uses
CONNECT_TIMEOUT_SECONDS/READ_TIMEOUT_SECONDS (the webhook HTTP client creation
logic in DiscordWebhookConfig) reads the injected values (prefer Duration or
seconds parsing) and document the property keys and sensible defaults.

In `@clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotifier.java`:
- Around line 18-23: notify 메서드가 DiscordWebhookRequest를 빈 페이로드(둘 다 없음)로 생성해 그대로
전송하므로, DiscordNotifier.notify에서 DiscordNotification.createContent()와
createEmbeds() 결과를 검사하여 둘 다 비어있거나 null이면 전송하지 않도록 유효성 가드를 추가하세요; 구체적으로 notify
내부에서 content와 embeds를 판정하고(예: null 또는 empty 목록/빈 문자열), 둘 다 비어있을 때
discordWebhookClient.send 호출을 건너뛰고 적절히 로그 또는 반환하도록 변경하세요(대상 심볼:
DiscordNotifier.notify, DiscordNotification.createContent,
DiscordNotification.createEmbeds, DiscordWebhookRequest,
discordWebhookClient.send).

In
`@clients/src/main/java/com/yapp/ndgl/clients/discord/request/DiscordWebhookRequest.java`:
- Around line 14-16: The record DiscordWebhookRequest currently assigns the
external mutable embeds List directly which can allow later mutation of the
payload; in the canonical constructor for DiscordWebhookRequest, defensively
copy the embeds list using List.copyOf(embeds) (or Collections.emptyList() when
null) and assign that to the embeds field, leaving the existing
TextUtils.truncate(content, DiscordPayloadConstraints.CONTENT_MAX) handling
intact so the record holds an unmodifiable snapshot of embeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 719de956-e562-4041-81b1-63f7f5aa8f95

📥 Commits

Reviewing files that changed from the base of the PR and between 51fc2ae and 9a1e5aa.

📒 Files selected for processing (19)
  • application/src/main/java/com/yapp/ndgl/application/common/aop/DistributedLockAspect.java
  • application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateCreatedEvent.java
  • application/src/main/java/com/yapp/ndgl/application/domains/travel/event/UserSuggestedTemplateNotification.java
  • application/src/main/java/com/yapp/ndgl/application/domains/travel/event/listener/TravelTemplateEventListener.java
  • application/src/main/java/com/yapp/ndgl/application/domains/travel/event/listener/UserSuggestedTemplateEventListener.java
  • application/src/main/java/com/yapp/ndgl/application/domains/travel/event/publisher/UserSuggestedTemplateEventPublisher.java
  • application/src/main/java/com/yapp/ndgl/application/domains/travel/facade/UserSuggestedTemplateFacade.java
  • application/src/main/java/com/yapp/ndgl/application/domains/travel/service/UserSuggestedTemplateService.java
  • application/src/main/resources/application.yml
  • clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotification.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordNotifier.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordPayloadConstraints.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/DiscordWebhookClient.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/config/DiscordWebhookConfig.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/config/DiscordWebhookProperties.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/request/DiscordEmbed.java
  • clients/src/main/java/com/yapp/ndgl/clients/discord/request/DiscordWebhookRequest.java
  • common/src/main/java/com/yapp/ndgl/common/util/TextUtils.java
  • domain/domain-rdb/src/main/java/com/yapp/ndgl/domain/common/lock/NamedLockRepository.java

Comment thread common/src/main/java/com/yapp/ndgl/common/util/TextUtils.java
@WooJJam WooJJam merged commit fc3f3e5 into develop May 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-generated AI로 생성한 PR feat 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant