Skip to content

feat: 어드민 인증 필요 시 알림을 보내도록 - #828

Merged
whqtker merged 6 commits into
developfrom
feat/735-discord-notification
Aug 5, 2026
Merged

feat: 어드민 인증 필요 시 알림을 보내도록#828
whqtker merged 6 commits into
developfrom
feat/735-discord-notification

Conversation

@whqtker

@whqtker whqtker commented Aug 5, 2026

Copy link
Copy Markdown
Member

관련 이슈

작업 내용

성적, 멘토 신청, 신고 시 디스코드로 알림이 오도록 구현했습니다.
parameter store에 웹훅 url 등록했습니다.

특이 사항

리뷰 요구사항 (선택)

@whqtker whqtker self-assigned this Aug 5, 2026
@whqtker whqtker added 기능 진행 중 자유롭게 merge 가능 labels Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

  1. 알림 기반 추가.
    Discord 알림 유형 열거형을 추가했습니다. Discord 웹훅으로 메시지를 비동기 전송하는 알림 컴포넌트를 추가했습니다.

  2. 서비스 흐름 연결.
    멘토 지원 저장 후 알림을 보냅니다. 신고 생성 후 알림을 보냅니다. GPA 저장 후 알림을 보냅니다. 어학 점수 저장 후 알림을 보냅니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: gyuhyeok99, sukangpunch, lsy1307

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 성적 등록, 멘토 신청, 신고 완료 시 Discord 알림을 전송하여 이슈 #735의 목표를 구현했습니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 성적, 멘토 신청, 신고에 대한 Discord 알림 기능과 직접 관련됩니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed 제목은 디스코드 알림 기능과 관련되지만, 실제 변경 사항인 성적·멘토 신청·신고 완료 알림을 구체적으로 설명하지 않습니다.
Description check ✅ Passed 관련 이슈와 주요 작업 내용이 작성되어 있으며, 선택 항목과 특이 사항이 비어 있지만 설명은 전반적으로 충분합니다.
✨ 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/735-discord-notification

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8729a79937

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> request = new HttpEntity<>(Map.of("content", buildMessage(type, applicantInfo)), headers);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Disable mentions for user-provided Discord content

When this notifier is called from score and mentor submissions, applicantInfo is siteUser.getNickname(), which users can control. Because the webhook payload only sends raw content, a nickname such as @everyone, @here, or a role mention will be parsed by Discord and can ping the admin channel whenever that user submits a score or mentor application; add allowed_mentions with an empty parse list or escape user-supplied text before sending.

Useful? React with 👍 / 👎.

@Value("${spring.profiles.active:}")
private String environment;

@Async

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep Discord task rejection from aborting requests

When the shared async executor is full (it is bounded in AsyncConfig and also used by S3/view-count work), calling an @Async method can be rejected before notify() enters its try/catch. In that case the transactional score, mentor, or report request can fail or roll back solely because the Discord notification could not be queued; wrap the proxy call/rejection or use a dedicated best-effort executor/rejection policy.

Useful? React with 👍 / 👎.

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

🤖 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
`@src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java`:
- Around line 28-29: Update DiscordNotifier’s message construction to omit the
environment prefix, including its brackets and spacing, when the injected
environment value is blank; retain the existing prefixed format when an
environment is configured.
- Around line 41-43: Update DiscordNotifier.notify(...) so the catch path no
longer only logs and drops failures; instead persist the failed alert or send
event to the existing outbox/queue flow, using DiscordNotifier as the entry
point and the current type/applicantInfo payload as the retry record. Add
deduplication and a bounded retry policy around the send operation, and route
exhausted retries to the failure-storage/alert path rather than swallowing the
exception.

In `@src/main/java/com/example/solidconnection/score/service/ScoreService.java`:
- Line 52: Move the Discord notification calls to transaction-commit event
handling so they execute only after the surrounding save transaction
successfully commits, preserving the existing notification types and recipients:
update ScoreService.java lines 52-52 and 65-65, MentorApplicationService.java
line 66-66, and ReportService.java line 45-45. Use an AFTER_COMMIT transaction
event or an equivalent outbox flow, and do not invoke DiscordNotifier.notify
directly within the transactional methods.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 72cd2f8c-4573-4601-ab75-967e9524def3

📥 Commits

Reviewing files that changed from the base of the PR and between 2a8ec34 and 8729a79.

📒 Files selected for processing (5)
  • src/main/java/com/example/solidconnection/common/discord/DiscordNotificationType.java
  • src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java
  • src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java
  • src/main/java/com/example/solidconnection/report/service/ReportService.java
  • src/main/java/com/example/solidconnection/score/service/ScoreService.java

Comment on lines +28 to +29
@Value("${spring.profiles.active:}")
private String environment;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

환경 값이 비어 있으면 접두사를 생략하세요.

spring.profiles.active의 기본값이 빈 문자열이므로 환경이 지정되지 않으면 메시지가 [] 학점 성적 검수 요청... 형식으로 시작합니다. 빈 환경을 유지하려면 환경 접두사 전체를 생략하세요.

Also applies to: 46-48

🤖 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
`@src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java`
around lines 28 - 29, Update DiscordNotifier’s message construction to omit the
environment prefix, including its brackets and spacing, when the injected
environment value is blank; retain the existing prefixed format when an
environment is configured.

Comment on lines +41 to +43
} catch (Exception e) {
log.error("Discord 검수 알림 전송 실패. type={}, applicantInfo={}", type, applicantInfo, e);
}

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching DiscordNotifier:"
fd -i 'DiscordNotifier' . || true

echo
echo "Git status/stat:"
git diff --stat || true

echo
echo "DiscordNotifier outline:"
file="$(fd -i '^DiscordNotifier\.java$' . | head -n1 || true)"
if [ -n "${file:-}" ]; then
  echo "FILE=$file"
  wc -l "$file"
  ast-grep outline "$file" || true
  echo
  echo "DiscordNotifier relevant lines:"
  cat -n "$file" | sed -n '1,180p'
fi

echo
echo "Search Discord notifier usages and related outbox/queue terms:"
rg -n "DiscordNotifier|sendExam|exam|outbox|queue|retries?|retry|DeadLetter|DLQ|실패|실패한|성공" src/main/java src/test/java 2>/dev/null || true

Repository: solid-connection/solid-connect-server

Length of output: 50394


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Project configuration files:"
git ls-files | rg '(^|/)(build\.gradle|build\.gradle\.kts|pom\.xml|gradle\.properties|settings\.gradle|docker-compose\.ya?ml|application.*\.ya?ml|application.*\.properties)$' || true

echo
echo "Search notification infrastructure:"
rg -n "AsyncEventListener|ApplicationEvent.|`@Transactional`|TransactionalEventListener|`@EventListener`|Outbox|Outbox|Queue|Queue|CloudEvent|Notification|Notifier\\.|sendExam|sendExam" src/main/java 2>/dev/null || true

Repository: solid-connection/solid-connect-server

Length of output: 17180


1. 실패한 Discord 알림을 저장하고 재시도하세요.

DiscordNotifier.notify(...)가 내부 예외를 로그에만 남기기 때문에 Discord 429, 5xx, 네트워크 오류는 알림 유실로 이어집니다. 이벤트 또는 전송 상태를 outbox/queue에 저장하고, 중복 전송 방지와 함께 제한된 재시도, 실패 보관/경보를 구현하세요.

  • 1 전송 실패 시 이벤트 또는 전송 기록을 outbox/queue에 저장하세요.
  • 2 중복 전송 방지와 제한된 재시도를 함께 구현하세요.
  • 3 재시도 모두 실패 시 실패 보관/알림 경로로 이동시키세요.
🤖 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
`@src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java`
around lines 41 - 43, Update DiscordNotifier.notify(...) so the catch path no
longer only logs and drops failures; instead persist the failed alert or send
event to the existing outbox/queue flow, using DiscordNotifier as the entry
point and the current type/applicantInfo payload as the retry record. Add
deduplication and a bounded retry policy around the send operation, and route
exhausted retries to the failure-storage/alert path rather than swallowing the
exception.

Gpa gpa = new Gpa(gpaScoreRequest.gpa(), gpaScoreRequest.gpaCriteria(), uploadedFile.fileUrl());
GpaScore newGpaScore = new GpaScore(gpa, siteUser);
GpaScore savedNewGpaScore = gpaScoreRepository.save(newGpaScore);
discordNotifier.notify(DiscordNotificationType.GPA_SCORE, siteUser.getNickname());

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Find candidate service files:\n'
fd -a 'ScoreService\.java|MentorApplicationService\.java|ReportService\.java|.*Notifier.*\.java|.*Notification.*\.java|.*Listener.*\.java|.*Async.*\.java' src/main/java 2>/dev/null | sed 's#^\./##' || true

printf '\nScoreService outline/contents:\n'
ast-grep outline src/main/java/com/example/solidconnection/score/service/ScoreService.java 2>/dev/null || true
cat -n src/main/java/com/example/solidconnection/score/service/ScoreService.java

printf '\nMentorApplicationService outline/contents:\n'
ast-grep outline src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java 2>/dev/null || true
cat -n src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java

printf '\nReportService outline/contents:\n'
ast-grep outline src/main/java/com/example/solidconnection/report/service/ReportService.java 2>/dev/null || true
cat -n src/main/java/com/example/solidconnection/report/service/ReportService.java

printf '\nSearch for DiscordNotifier implementation and async/disk notification handling:\n'
rg -n "interface DiscordNotifier|class .*Discord|notify\\(|`@Async`|ApplicationEvent|AFTER_COMMIT|TransactionSynchronizationManager|TransactionManager|EventPublisher|save\\(" src/main/java src/test/java 2>/dev/null | head -n 200

Repository: solid-connection/solid-connect-server

Length of output: 38630


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'DiscordNotifier implementation:\n'
cat -n src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java

printf '\nAsync transactional configuration:\n'
cat -n src/main/java/com/example/solidconnection/common/config/sync/AsyncConfig.java

printf '\nStatic verifier: direct Discord notifier calls from `@Transactional` save paths:\n'
python3 - <<'PY'
from pathlib import Path
import re

targets = [
    ('src/main/java/com/example/solidconnection/score/service/ScoreService.java', [52, 65]),
    ('src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java', [66]),
    ('src/main/java/com/example/solidconnection/report/service/ReportService.java', [45]),
]
notifier = Path('src/main/java/com/example/solidconnection/common/discord/DiscordNotifier.java').read_text()

for file, lines in targets:
    src = Path(file).read_text().splitlines()
    in_transactional = False
    for i, line in enumerate(src, 1):
        if '`@Transactional`' in line:
            in_transactional = True
        if i in lines:
            call = line.strip()
            print(f'{file}:{i}: transactional_context={in_transactional} call={call}')
        if 'public ' in line and 'void' in line or (i in lines and in_transactional):
            in_transactional = 'return' not in line or 'void' not in line

has_async_notify = bool(re.search(r'@\s*Async[\s\n]*public\s+void\s+notify\b', notifier))
has_spring_async_annotation = bool(re.search(r'org\.springframework\..scheduling\.\w+\.annotation\.\s*`@Async`', Path('pom.xml').read_text() if Path('pom.xml').exists() else ''))
print(f'DiscordNotifier.notify is `@Async`={has_async_notify}')
print(f'Service paths dispatch outside transaction commit boundary; Spring `@Async` tasks queued while current request path returns before notification completion.')
PY

Repository: solid-connection/solid-connect-server

Length of output: 4433


1. Discord 알림 트랜잭션 커밋과 분리하세요.

네 위치 모두 @Async DiscordNotifier.notify@Transactional save 후에 실행됩니다. 기존 등록/수정/삭제가 rollback되면 알림이 누락되거나, 실제 데이터 링크는 커밋이 완성되지 않은 상태로 전송될 수 있습니다. transaction event를 AFTER_COMMIT에서 처리하거나 outbox로 저장 후 발송하세요.

- src/main/java/com/example/solidconnection/score/service/ScoreService.java#L52: GPA 알림을 커밋 이후 이벤트로 변경하세요.
- src/main/java/com/example/solidconnection/score/service/ScoreService.java#L65: 어학 성적 알림을 커밋 이후 이벤트로 변경하세요.
- src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java#L66: 멘토 지원 알림을 커밋 이후 이벤트로 변경하세요.
- src/main/java/com/example/solidconnection/report/service/ReportService.java#L45: 신고 알림을 커밋 이후 이벤트로 변경하세요.

[low Effort_and_high_reward]

📍 Affects 3 files
  • src/main/java/com/example/solidconnection/score/service/ScoreService.java#L52-L52 (this comment)
  • src/main/java/com/example/solidconnection/score/service/ScoreService.java#L65-L65
  • src/main/java/com/example/solidconnection/mentor/service/MentorApplicationService.java#L66-L66
  • src/main/java/com/example/solidconnection/report/service/ReportService.java#L45-L45
🤖 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 `@src/main/java/com/example/solidconnection/score/service/ScoreService.java` at
line 52, Move the Discord notification calls to transaction-commit event
handling so they execute only after the surrounding save transaction
successfully commits, preserving the existing notification types and recipients:
update ScoreService.java lines 52-52 and 65-65, MentorApplicationService.java
line 66-66, and ReportService.java line 45-45. Use an AFTER_COMMIT transaction
event or an equivalent outbox flow, and do not invoke DiscordNotifier.notify
directly within the transactional methods.

@whqtker
whqtker merged commit e0ebcb1 into develop Aug 5, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

기능 진행 중 자유롭게 merge 가능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 어드민 인증 필요 시 디스코드로 알림을 보내도록

1 participant