Skip to content

Conversation

@SeongHo5356
Copy link
Contributor

@SeongHo5356 SeongHo5356 commented Jan 16, 2026

🚀 Why - 해결하려는 문제가 무엇인가요?

  • SRLT-132 백오피스에서 운영/기획자가 회원에게 메일을 전송할 수 있어야 했습니다.
  • 백오피스 기능은 일반 사용자(JWT) 흐름과 분리되어야 하므로, 백오피스 전용 로그인/권한(ROLE_BACKOFFICE) 기반으로 보호할 필요가 있었습니다.
  • 메일 전송 시 성공/실패 결과를 남겨야 했고, 로그 저장 로직을 메인 전송 로직과 분리해 유지보수성을 높이고자 했습니다.
  • CORS 설정에 백오피스(office) 주소가 포함되지 않아, 운영 환경에서 호출이 막힐 수 있어 허용 origin 추가가 필요했습니다.

✅ What - 무엇이 변경됐나요?

  • 백오피스 메일 기능
    • 메일 전송 API 추가/정리: POST /v1/backoffice/mail/send
    • 메일 템플릿 관리 API: POST/GET/DELETE /v1/backoffice/mail/templates
  • 로그 저장 분리
    • 메일 전송 결과(성공/실패)를 BackofficeMailSendEvent로 발행하고,
    • @EventListener에서 BackofficeMailSendLog를 저장하도록 분리
  • 보안/인증
    • /v1/backoffice/mail/** + /login + /logout에 대해 백오피스 전용 SecurityFilterChain(@order(1)) 구성
    • in-memory 계정(backoffice.auth.username/password) + ROLE_BACKOFFICE 기반 접근 제어
    • session + CSRF(cookie) 기반으로 로그인 유지
  • CORS
    • cors.origin.office를 추가하고, 허용 origin에 office 주소 포함
  • 리팩토링
    • 백오피스 메일 관련 DTO 생성 로직을 정적 팩토리(of) 형태로 통일

API 요약

구분 Method Endpoint 설명
메일 전송 POST /v1/backoffice/mail/send 수신자(to)/제목/본문(html or text) 기반 메일 전송
템플릿 생성 POST /v1/backoffice/mail/templates 템플릿 저장
템플릿 조회 GET /v1/backoffice/mail/templates 템플릿 목록 조회
템플릿 삭제 DELETE /v1/backoffice/mail/templates/{templateId} 템플릿 삭제

🛠️ How - 어떻게 해결했나요?

  • 메일 전송/검증 + 이벤트 발행

    1. 요청값(to/subject/contentType 등) 검증 후 MailSenderPort로 실제 전송
    2. 성공 시 BackofficeMailSendEvent(success=true) 발행
    3. 실패(IllegalArgumentException/기타 Exception) 시 BackofficeMailSendEvent(success=false, errorMessage=...) 발행 후 BackofficeException으로 실패 응답 처리
  • 로그 저장 분리(@eventlistener)

    • BackofficeMailSendEvent를 수신하면 recipients를 join하여 BackofficeMailSendLog 도메인을 생성하고 저장
  • 백오피스 전용 보안 체인 분리

    • securityMatcher("/v1/backoffice/mail/**", "/login", "/logout")로 백오피스 경로는 별도 체인에서 처리
    • anyRequest().hasRole("BACKOFFICE")로 보호하고 formLogin()으로 로그인 진입
    • 계정은 properties 기반(in-memory)으로 구성하여 운영에서 간단히 적용 가능
  • DTO 정적 팩토리 통일

    • new ...Input(...) 형태 대신 ...Input.of(...) 형태로 생성 규칙을 통일해 가독성/일관성 개선

🖼️ Attachment

https://office.starlight-official.co.kr

💬 기타 코멘트

  • 테스트 방법

    • 백오피스 계정으로 로그인 후(/login), 아래 API 호출:
      • POST /v1/backoffice/mail/send
      • POST/GET/DELETE /v1/backoffice/mail/templates
    • 실패 케이스(잘못된 contentType / 수신자 비어있음 등)에서도 이벤트 기반으로 실패 로그가 저장되는지 확인
    • office 도메인에서 CORS 정상 동작 확인
  • 주의할 점

    • 백오피스 경로는 session + CSRF 기반이라, 호출 클라이언트(백오피스 UI)에서 쿠키/CSRF 처리 방식에 맞춰 호출해야 합니다.
    • 현재 이벤트 핸들러는 @EventListener로 동작하며(동기), 추후 필요 시 @Async/@TransactionalEventListener로 확장 가능.

Summary by CodeRabbit

  • 새로운 기능

    • 백오피스 이메일 발송(HTML/텍스트), 발송 이벤트·발송 로그, 템플릿 생성·조회·삭제 기능 추가
  • 인프라 / API

    • 백오피스용 REST API(발송·템플릿 관리) 및 JavaMail 기반 실제 메일 전송 연동 추가
  • 보안

    • 백오피스 전용 보안 체인·인증 설정 및 CORS 허용 확장 적용
  • 품질

    • 입력 유효성 강화, 상세 오류 타입·예외 체계 및 발송 성공/실패 기록(로그) 기능 추가

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between 1677d6c and b70d0a1.

📒 Files selected for processing (4)
  • src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailSendRequest.java
  • src/main/java/starlight/domain/backoffice/exception/BackofficeException.java
  • src/main/java/starlight/shared/apiPayload/exception/GlobalException.java
 _____________________________________________________________
< Jurassic Park would have been a lot shorter with me around. >
 -------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

✏️ Tip: You can disable in-progress messages and the fortune message in your review settings.

Tip

You can disable the changed files summary in the walkthrough.

Disable the reviews.changed_files_summary setting in your project's settings in CodeRabbit to disable the changed files summary in the walkthrough.

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: 'tools'
⚙️ 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

Walkthrough

백오피스 이메일 기능 추가: SMTP 전송 어댑터와 전송 서비스/유스케이스, 템플릿 CRUD JPA 어댑터·리포지토리, 발송 로그 엔티티·저장, 이벤트 발행/핸들러, 백오피스 전용 보안 체인 및 인증 빈이 추가되었고 서브모듈 포인터가 업데이트되었습니다. (50단어 이내)

Changes

Cohort / File(s) 변경 요약
메일 전송 어댑터
src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java
JavaMailSender 기반 SMTP 구현 추가 — sender 주입(@Value), HTML/TEXT 분기, MimeMessage 구성·전송, MessagingException 처리 및 BackofficeException 변환
템플릿 영속화 계층
src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateJpa.java, src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateRepository.java
JPA 리포지토리 및 어댑터 추가 — save/deleteById/findAll을 repository로 위임
발송 로그 영속화 계층
src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogJpa.java, src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogRepository.java
발송 로그 저장용 JPA 리포지토리·어댑터 추가 — save 위임
웹 API 컨트롤러·DTOs
src/main/java/starlight/adapter/backoffice/mail/webapi/BackofficeMailController.java, .../dto/request/BackofficeMailSendRequest.java, .../BackofficeMailTemplateCreateRequest.java, .../dto/response/BackofficeMailTemplateResponse.java
전송·템플릿 엔드포인트 추가(전송/템플릿 생성/조회/삭제), 요청 유효성 검사 및 DTO↔Input 변환 구현
애플리케이션 서비스·이벤트
src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java, .../BackofficeMailTemplateService.java, .../BackofficeMailSendLogEventHandler.java, .../event/BackofficeMailSendEvent.java
서비스 레이어 추가: contentType 파싱·검증, MailSenderPort 위임, 성공/실패 이벤트 발행, 이벤트 핸들러에서 로그 생성·저장
포트·유스케이스·애플리케이션 DTOs
src/main/java/starlight/application/backoffice/mail/required/*.java, src/main/java/starlight/application/backoffice/mail/provided/*.java, src/main/java/starlight/application/backoffice/mail/provided/dto/*/*.java
MailSenderPort, 템플릿/전송 유스케이스와 입력/결과 레코드(팩토리 포함) 추가 — 계층 간 계약 정의
도메인: 모델·엔티티·예외
src/main/java/starlight/domain/backoffice/mail/*, src/main/java/starlight/domain/backoffice/exception/*
BackofficeMailContentType enum, BackofficeMailTemplate 및 BackofficeMailSendLog JPA 엔티티(생성 팩토리 포함), BackofficeErrorType enum 및 BackofficeException 추가
보안 구성
src/main/java/starlight/bootstrap/SecurityConfig.java
백오피스 전용 SecurityFilterChain(@Order(1)) 추가 — CSRF/CORS 설정, in-memory UserDetailsService 및 DaoAuthenticationProvider 빈 등록, officeBaseUrl CORS 허용
서브모듈 포인터
.gitmodules (ref)
서브모듈 커밋 포인터 업데이트 (비기능적)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as BackofficeMailController
    participant UseCase as BackofficeMailSendUseCase
    participant Service as BackofficeMailSendService
    participant MailSender as MailSenderPort
    participant EventBus as ApplicationEventPublisher
    participant EventHandler as BackofficeMailSendLogEventHandler
    participant LogPort as BackofficeMailSendLogCommandPort

    Client->>Controller: POST /v1/backoffice/mail/send
    Controller->>UseCase: send(input)
    UseCase->>Service: send(input)
    Service->>Service: parseContentType(), validate()
    alt valid
        Service->>MailSender: send(input, contentType)
        MailSender-->>Service: success
        Service->>EventBus: publishEvent(success=true)
        EventBus->>EventHandler: handle(event)
        EventHandler->>LogPort: save(log)
        LogPort-->>EventHandler: saved
        Service-->>Controller: success response
    else failure
        Service->>EventBus: publishEvent(success=false, error)
        EventBus->>EventHandler: handle(event)
        EventHandler->>LogPort: save(log)
        Service-->>Controller: throws BackofficeException
    end
    Controller-->>Client: ApiResponse
Loading
sequenceDiagram
    participant Client
    participant Controller as BackofficeMailController
    participant UseCase as BackofficeMailTemplateUseCase
    participant Service as BackofficeMailTemplateService
    participant CommandPort as BackofficeMailTemplateCommandPort
    participant QueryPort as BackofficeMailTemplateQueryPort

    Client->>Controller: POST /v1/backoffice/mail/templates
    Controller->>UseCase: createTemplate(input)
    UseCase->>Service: createTemplate(input)
    Service->>Service: parseContentType()
    Service->>CommandPort: save(template)
    CommandPort-->>Service: saved
    Service-->>Controller: BackofficeMailTemplateResult

    Client->>Controller: GET /v1/backoffice/mail/templates
    Controller->>UseCase: findTemplates()
    UseCase->>Service: findTemplates()
    Service->>QueryPort: findAll()
    QueryPort-->>Service: List<template>
    Service-->>Controller: List<Result>

    Client->>Controller: DELETE /v1/backoffice/mail/templates/{id}
    Controller->>UseCase: deleteTemplate(id)
    UseCase->>Service: deleteTemplate(id)
    Service->>CommandPort: deleteById(id)
    Service-->>Controller: success response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

✨ FEAT

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 'SRLT-132 백오피스에서 메일을 전송한다'는 이슈 추적 식별자와 변경의 핵심을 명확히 반영하고 있으며, 메일 전송 기능 추가라는 주요 변경사항을 정확히 요약하고 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 Jan 16, 2026

Test Results

249 tests   249 ✅  10s ⏱️
 46 suites    0 💤
 46 files      0 ❌

Results for commit b70d0a1.

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

🤖 Fix all issues with AI agents
In `@config`:
- Line 1: SecurityConfig.java references cors.origin.office but that key lives
in the external config submodule; verify the external repo's application.yml
includes "cors: origin: office: ${BACKOFFICE_ORIGIN:http://localhost:3001}" and
ensure your CI/CD and deploy scripts initialize and update the submodule (e.g.
git submodule update --init --recursive) before build so the setting is present;
also confirm BACKOFFICE_ORIGIN env var behavior in the environment or add a safe
fallback in SecurityConfig.java if the key is missing at runtime.

In `@src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java`:
- Around line 26-44: The catch block in SmtpMailSender.send converts
MessagingException into IllegalArgumentException which causes the failure to be
classified as INVALID_MAIL_REQUEST; instead, keep the exception as an unchecked
runtime error indicating mail send failure by replacing the thrown
IllegalArgumentException with an unchecked MailSendFailedException (create a new
class MailSendFailedException extends RuntimeException) or throw a
RuntimeException with the original MessagingException as the cause; ensure you
log the error as you already do (log.error(..., e)) and rethrow the new
unchecked exception from the send method so failures are classified as
MAIL_SEND_FAILED.

In
`@src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailSendRequest.java`:
- Around line 10-22: BackofficeMailSendRequest allows both html and text to be
blank, enabling empty emails; add cross-field validation to require at least one
non-blank body. Implement either a custom class-level constraint (e.g.,
`@AtLeastOneBody`) or an `@AssertTrue` boolean method on BackofficeMailSendRequest
that checks StringUtils.hasText(html) || StringUtils.hasText(text) and provides
a clear message like "either html or text must be provided"; keep the toInput()
and BackofficeMailSendInput.of invocation unchanged but ensure validation is
applied before conversion (class-level annotation or standard javax.validation
setup).

In
`@src/main/java/starlight/application/backoffice/mail/BackofficeMailSendLogEventHandler.java`:
- Around line 16-26: The current BackofficeMailSendEventHandler.handle uses a
synchronous `@EventListener` and directly calls logCommandPort.save(log), which
can propagate exceptions back to the mail flow; change the handler to run
asynchronously and after transaction commit by annotating the handler with
`@Async`("emailTaskExecutor") and `@TransactionalEventListener`(phase =
TransactionPhase.AFTER_COMMIT) (replace or supplement the existing
`@EventListener`), and wrap the call to logCommandPort.save(log) in a try-catch
that logs any exception (without rethrowing) to ensure failures during
BackofficeMailSendLog.create/ logCommandPort.save do not affect mail sending;
reference symbols: BackofficeMailSendEvent,
BackofficeMailSendLogEventHandler.handle, BackofficeMailSendLog.create, and
logCommandPort.save.

In `@src/main/java/starlight/domain/backoffice/mail/BackofficeMailSendLog.java`:
- Around line 18-32: The recipients field in BackofficeMailSendLog is storing
raw email addresses as TEXT (recipients) which raises PII risk; before
persisting change the design: consult and codify the privacy/retention policy,
then implement one of the approved approaches (store masked emails, hash/encrypt
the recipients column, or store only a non-PII identifier) and add access
controls and retention/deletion logic; update the BackofficeMailSendLog model
(recipients) to reflect the chosen storage format and add clear
documentation/comments and any required encryption/decryption helper usage or
annotations so callers use the safe API.
🧹 Nitpick comments (9)
src/main/java/starlight/application/backoffice/mail/provided/dto/input/BackofficeMailSendInput.java (1)

5-20: 수신자 리스트 불변 보장을 권장합니다.
List<String> to가 외부에서 변경되면 발송 대상이 변질될 수 있습니다. 생성 시 방어적 복사를 권장합니다.

♻️ 제안 수정
 public record BackofficeMailSendInput(
         List<String> to,
         String subject,
         String contentType,
         String html,
         String text
 ) {
+    public BackofficeMailSendInput {
+        to = List.copyOf(to);
+    }
     public static BackofficeMailSendInput of(
             List<String> to,
             String subject,
             String contentType,
             String html,
             String text
     ) {
         return new BackofficeMailSendInput(to, subject, contentType, html, text);
     }
 }
src/main/java/starlight/domain/backoffice/mail/BackofficeMailContentType.java (1)

8-24: contentType 입력값 공백 처리 개선 제안

요청 값에 공백이 포함되면 유효한 값도 실패합니다. 사소하지만 trim 후 비교하면 안정성이 올라갑니다.

♻️ 개선 예시
     public static BackofficeMailContentType from(String value) {
         if (value == null) {
             throw new IllegalArgumentException("contentType is required");
         }
-        if ("html".equalsIgnoreCase(value)) {
+        String normalized = value.trim();
+        if ("html".equalsIgnoreCase(normalized)) {
             return HTML;
         }
-        if ("text".equalsIgnoreCase(value)) {
+        if ("text".equalsIgnoreCase(normalized)) {
             return TEXT;
         }
         throw new IllegalArgumentException("invalid contentType");
     }
src/main/java/starlight/domain/backoffice/exception/BackofficeErrorType.java (1)

12-18: 에러 타입 이름을 도메인+4자리 코드로 맞춰주세요.

현재 상수명이 설명형이라 에러 코드 체계와 불일치합니다. 도메인명+4자리 숫자 형태로 맞추면 식별성과 운영 대응이 좋아집니다. 번호는 기존 백오피스 에러 코드 레지스트리에 맞춰 배정하고 참조부도 함께 갱신해 주세요. Based on learnings, ErrorType 네이밍 컨벤션을 맞춰주세요.

♻️ 예시 변경안
-    INVALID_MAIL_CONTENT_TYPE(HttpStatus.BAD_REQUEST, "유효하지 않은 contentType입니다."),
-    INVALID_MAIL_REQUEST(HttpStatus.BAD_REQUEST, "메일 발송 요청이 유효하지 않습니다."),
-    MAIL_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "메일 전송에 실패했습니다."),
-    MAIL_TEMPLATE_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "메일 템플릿 저장에 실패했습니다."),
-    MAIL_TEMPLATE_QUERY_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "메일 템플릿 조회에 실패했습니다."),
-    MAIL_TEMPLATE_DELETE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "메일 템플릿 삭제에 실패했습니다."),
-    MAIL_LOG_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "메일 로그 저장에 실패했습니다."),
+    BACKOFFICE4001(HttpStatus.BAD_REQUEST, "유효하지 않은 contentType입니다."),
+    BACKOFFICE4002(HttpStatus.BAD_REQUEST, "메일 발송 요청이 유효하지 않습니다."),
+    BACKOFFICE5001(HttpStatus.INTERNAL_SERVER_ERROR, "메일 전송에 실패했습니다."),
+    BACKOFFICE5002(HttpStatus.INTERNAL_SERVER_ERROR, "메일 템플릿 저장에 실패했습니다."),
+    BACKOFFICE5003(HttpStatus.INTERNAL_SERVER_ERROR, "메일 템플릿 조회에 실패했습니다."),
+    BACKOFFICE5004(HttpStatus.INTERNAL_SERVER_ERROR, "메일 템플릿 삭제에 실패했습니다."),
+    BACKOFFICE5005(HttpStatus.INTERNAL_SERVER_ERROR, "메일 로그 저장에 실패했습니다."),
src/main/java/starlight/domain/backoffice/mail/BackofficeMailSendLog.java (1)

34-47: success와 errorMessage의 일관성 검증을 추가하면 로그 품질이 좋아집니다.

성공인데 errorMessage가 남거나, 실패인데 메시지가 비어 있을 수 있습니다. 성공 시 null, 실패 시 필수 메시지로 제한하면 조회/분석 시 혼선이 줄어듭니다.

♻️ 제안 변경안
         Assert.notNull(contentType, "contentType must not be null");

         BackofficeMailSendLog log = new BackofficeMailSendLog();
         log.recipients = recipients;
         log.emailTitle = emailTitle;
         log.contentType = contentType;
         log.success = success;
-        log.errorMessage = errorMessage;
+        if (success) {
+            log.errorMessage = null;
+        } else {
+            Assert.hasText(errorMessage, "errorMessage must not be empty");
+            log.errorMessage = errorMessage;
+        }

         return log;
src/main/java/starlight/domain/backoffice/mail/BackofficeMailTemplate.java (1)

34-46: contentType에 맞는 본문 유효성 검증 추가 제안

현재 팩토리는 name/emailTitle/contentType만 검증하고 html/text는 비어 있어도 저장됩니다. contentType이 HTML/TEXT인데 본문이 비어 있으면 빈 템플릿이 저장될 수 있으니 도메인에서 최소 한쪽 본문을 강제하는 편이 안전합니다.

♻️ 예시 수정안
     Assert.hasText(name, "name must not be empty");
     Assert.hasText(emailTitle, "title must not be empty");
     Assert.notNull(contentType, "contentType must not be null");
+    boolean hasHtml = html != null && !html.isBlank();
+    boolean hasText = text != null && !text.isBlank();
+    Assert.isTrue(hasHtml || hasText, "html or text must not be empty");
src/main/java/starlight/application/backoffice/mail/BackofficeMailTemplateService.java (1)

76-83: contentType 소문자 변환은 Locale 고정 권장
name().toLowerCase()는 서버 로케일 영향(예: 터키어 i)을 받을 수 있어요. Locale.ROOT로 고정하면 예측 가능한 결과를 보장합니다.

♻️ 제안 변경
-import java.util.List;
+import java.util.List;
+import java.util.Locale;
...
-                template.getContentType().name().toLowerCase(),
+                template.getContentType().name().toLowerCase(Locale.ROOT),
src/main/java/starlight/application/backoffice/mail/event/BackofficeMailSendEvent.java (1)

7-22: 이벤트 수신자 리스트 불변성/널 안전성 보강 권장
현재 List<String>이 외부에서 변경 가능하고 null일 경우 후속 처리에서 NPE 위험이 있습니다. 레코드 컴팩트 생성자에서 방어적 복사와 null 기본값을 적용해주세요.

♻️ 제안 변경
 public record BackofficeMailSendEvent(
         List<String> to,
         String subject,
         BackofficeMailContentType contentType,
         boolean success,
         String errorMessage
 ) {
+    public BackofficeMailSendEvent {
+        to = (to == null) ? List.of() : List.copyOf(to);
+    }
     public static BackofficeMailSendEvent of(
             List<String> to,
             String subject,
             BackofficeMailContentType contentType,
             boolean success,
             String errorMessage
     ) {
         return new BackofficeMailSendEvent(to, subject, contentType, success, errorMessage);
     }
 }
src/main/java/starlight/bootstrap/SecurityConfig.java (1)

163-171: 백오피스 비밀번호 포맷을 명확히 해주세요.
현재는 프로퍼티 값을 매번 encode 하므로 설정값이 이미 해시라면 로그인 실패가 납니다. 운영 설정이 평문인지 문서화하거나, 해시 입력을 허용하는 전략(예: DelegatingPasswordEncoder + {bcrypt})을 검토해 주세요.

src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java (1)

23-46: 이벤트 로깅 실패가 전송 결과를 왜곡할 수 있습니다.
동기 리스너 예외가 전파되면 메일 전송이 성공해도 MAIL_SEND_FAILED로 처리될 수 있습니다. 이벤트 리스너를 @Async/@TransactionalEventListener(AFTER_COMMIT)로 분리하거나, 로그 이벤트 발행 실패를 별도로 처리하는 방식을 검토해 주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fd012a and 39f4e87.

📒 Files selected for processing (29)
  • config
  • src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogJpa.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogRepository.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateJpa.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateRepository.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/BackofficeMailController.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailSendRequest.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailTemplateCreateRequest.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/response/BackofficeMailTemplateResponse.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailSendLogEventHandler.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailTemplateService.java
  • src/main/java/starlight/application/backoffice/mail/event/BackofficeMailSendEvent.java
  • src/main/java/starlight/application/backoffice/mail/provided/BackofficeMailSendUseCase.java
  • src/main/java/starlight/application/backoffice/mail/provided/BackofficeMailTemplateUseCase.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/input/BackofficeMailSendInput.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/input/BackofficeMailTemplateCreateInput.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/result/BackofficeMailTemplateResult.java
  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailSendLogCommandPort.java
  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailTemplateCommandPort.java
  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailTemplateQueryPort.java
  • src/main/java/starlight/application/backoffice/mail/required/MailSenderPort.java
  • src/main/java/starlight/bootstrap/SecurityConfig.java
  • src/main/java/starlight/domain/backoffice/exception/BackofficeErrorType.java
  • src/main/java/starlight/domain/backoffice/exception/BackofficeException.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailContentType.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailSendLog.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailTemplate.java
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-01-11T16:13:19.742Z
Learnt from: 2ghrms
Repo: StartUpLight/STARLIGHT_BE PR: 77
File: src/test/java/starlight/application/member/CredentialServiceUnitTest.java:20-21
Timestamp: 2026-01-11T16:13:19.742Z
Learning: StarLight 프로젝트에서 헥사고날 아키텍처 도입 시 인터페이스 및 구현체 네이밍 규칙을 적용합니다. 기존 Service 인터페이스는 UseCase로 이름을 변경하고, ServiceImpl 구현 클래스는 Service로 이름을 변경합니다. 예: CredentialService 인터페이스 → CredentialUseCase, CredentialServiceImpl → CredentialService. 이 규칙은 프로젝트의 모든 Java 파일에 일관되게 적용되어야 하며, 관련 참조 및 코드 호출부도 동일한 네이밍으로 업데이트해야 합니다.

Applied to files:

  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailTemplateQueryPort.java
  • src/main/java/starlight/domain/backoffice/exception/BackofficeException.java
  • src/main/java/starlight/application/backoffice/mail/required/MailSenderPort.java
  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailTemplateCommandPort.java
  • src/main/java/starlight/application/backoffice/mail/event/BackofficeMailSendEvent.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/input/BackofficeMailTemplateCreateInput.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailSendLog.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogRepository.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailTemplate.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailTemplateCreateRequest.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailSendRequest.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/result/BackofficeMailTemplateResult.java
  • src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/BackofficeMailController.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogJpa.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/response/BackofficeMailTemplateResponse.java
  • src/main/java/starlight/application/backoffice/mail/provided/BackofficeMailTemplateUseCase.java
  • src/main/java/starlight/application/backoffice/mail/provided/BackofficeMailSendUseCase.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateJpa.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateRepository.java
  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailSendLogCommandPort.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailContentType.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailTemplateService.java
  • src/main/java/starlight/bootstrap/SecurityConfig.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/input/BackofficeMailSendInput.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailSendLogEventHandler.java
  • src/main/java/starlight/domain/backoffice/exception/BackofficeErrorType.java
📚 Learning: 2026-01-11T16:13:35.380Z
Learnt from: 2ghrms
Repo: StartUpLight/STARLIGHT_BE PR: 77
File: src/test/java/starlight/application/member/CredentialServiceIntegrationTest.java:20-20
Timestamp: 2026-01-11T16:13:35.380Z
Learning: Enforce the naming convention introduced by SRLT-124: all use-case interfaces must end with the suffix UseCase, while their concrete implementations must drop the Impl suffix and use the base name without UseCase (e.g., CredentialUseCase as the interface, CredentialService as the concrete implementation). In code reviews, verify that: - interface names end with UseCase; - concrete classes implementing those interfaces are named [BaseName]Service; - any class named [BaseName]UseCase or [BaseName]Impl is avoided in implementation files; - relationship between interface and implementation is clear and consistent across src/main/java. In test files, ensure integration tests reference the concrete *Service* implementations (e.g., CredentialService), not interfaces, unless explicitly testing the interface type.

Applied to files:

  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailTemplateQueryPort.java
  • src/main/java/starlight/domain/backoffice/exception/BackofficeException.java
  • src/main/java/starlight/application/backoffice/mail/required/MailSenderPort.java
  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailTemplateCommandPort.java
  • src/main/java/starlight/application/backoffice/mail/event/BackofficeMailSendEvent.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/input/BackofficeMailTemplateCreateInput.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailSendLog.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogRepository.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailTemplate.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailTemplateCreateRequest.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailSendRequest.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/result/BackofficeMailTemplateResult.java
  • src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/BackofficeMailController.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogJpa.java
  • src/main/java/starlight/adapter/backoffice/mail/webapi/dto/response/BackofficeMailTemplateResponse.java
  • src/main/java/starlight/application/backoffice/mail/provided/BackofficeMailTemplateUseCase.java
  • src/main/java/starlight/application/backoffice/mail/provided/BackofficeMailSendUseCase.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateJpa.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java
  • src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateRepository.java
  • src/main/java/starlight/application/backoffice/mail/required/BackofficeMailSendLogCommandPort.java
  • src/main/java/starlight/domain/backoffice/mail/BackofficeMailContentType.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailTemplateService.java
  • src/main/java/starlight/bootstrap/SecurityConfig.java
  • src/main/java/starlight/application/backoffice/mail/provided/dto/input/BackofficeMailSendInput.java
  • src/main/java/starlight/application/backoffice/mail/BackofficeMailSendLogEventHandler.java
  • src/main/java/starlight/domain/backoffice/exception/BackofficeErrorType.java
📚 Learning: 2025-10-26T16:17:33.090Z
Learnt from: 2ghrms
Repo: StartUpLight/STARLIGHT_BE PR: 21
File: src/main/java/starlight/adapter/ncp/ocr/exception/OcrErrorType.java:11-16
Timestamp: 2025-10-26T16:17:33.090Z
Learning: StarLight 프로젝트에서는 ErrorType enum의 이름을 "도메인명 + 4자리 숫자" 형식으로 작성합니다 (예: OCR4001, BUSINESSPLAN4002). 이는 HTTP 상태 코드만으로는 구분이 어려운 세부 에러를 명확히 식별하기 위한 컨벤션입니다.

Applied to files:

  • src/main/java/starlight/domain/backoffice/exception/BackofficeErrorType.java
🧬 Code graph analysis (5)
src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java (2)
src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java (1)
  • Service (16-89)
src/main/java/starlight/application/backoffice/mail/BackofficeMailTemplateService.java (1)
  • Service (19-87)
src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogJpa.java (1)
src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateJpa.java (1)
  • Repository (11-31)
src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateJpa.java (1)
src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogJpa.java (1)
  • Repository (8-18)
src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java (2)
src/main/java/starlight/application/backoffice/mail/BackofficeMailTemplateService.java (1)
  • Service (19-87)
src/main/java/starlight/domain/backoffice/exception/BackofficeException.java (1)
  • BackofficeException (6-11)
src/main/java/starlight/application/backoffice/mail/BackofficeMailTemplateService.java (2)
src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java (1)
  • Service (16-89)
src/main/java/starlight/domain/backoffice/exception/BackofficeException.java (1)
  • BackofficeException (6-11)
🪛 GitHub Actions: Deployment Workflow
config

[error] 1-1: Failed to inject dependencies due to unresolved placeholder 'cors.origin.office'.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (26)
src/main/java/starlight/domain/backoffice/exception/BackofficeException.java (1)

6-10: LGTM: 백오피스 전용 예외 타입 추가가 깔끔합니다.
단순 래핑 구조로 문제 없어 보입니다.

src/main/java/starlight/application/backoffice/mail/provided/dto/input/BackofficeMailTemplateCreateInput.java (1)

3-18: LGTM: 입력 DTO 구성이 명확합니다.
정적 팩토리도 사용성 측면에서 무난합니다.

src/main/java/starlight/application/backoffice/mail/provided/BackofficeMailSendUseCase.java (1)

5-7: LGTM: UseCase 인터페이스 역할이 명확합니다.

src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailTemplateCreateRequest.java (1)

5-16: 템플릿 본문 검증 위치 확인 필요

현재 DTO는 name/title/contentType만 필수이고 html/text는 공백 허용입니다. BackofficeMailTemplateService.createTemplate에서도 본문 조건부 검증이 보이지 않으니, 도메인/서비스에서 contentType별 본문 필수 검증이 있는지 확인해주세요. 없다면 DTO 커스텀 검증 또는 서비스 검증 추가가 필요합니다.

src/main/java/starlight/application/backoffice/mail/provided/dto/result/BackofficeMailTemplateResult.java (1)

5-32: 결과 DTO 구성 및 팩토리 메서드 무난합니다

필드 구성과 of(...) 팩토리가 응답 매핑에 적합하게 정리되어 있습니다.

src/main/java/starlight/application/backoffice/mail/required/MailSenderPort.java (1)

6-8: 포트 계약이 명확합니다

메일 전송 포트 시그니처가 간결하고 사용 의도가 분명합니다.

src/main/java/starlight/application/backoffice/mail/required/BackofficeMailSendLogCommandPort.java (1)

5-7: 커맨드 포트 정의가 단순하고 의도가 명확합니다.

저장 책임만 노출한 점이 깔끔합니다.

src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogRepository.java (1)

6-6: 저장 리포지토리 선언이 깔끔합니다.

구조가 단순해 확장도 용이해 보입니다.

src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailSendLogJpa.java (1)

10-16: 포트-어댑터 연결이 명확하고 일관됩니다.

다른 JPA 어댑터 패턴과 유사해 읽기 쉽습니다.

src/main/java/starlight/application/backoffice/mail/required/BackofficeMailTemplateQueryPort.java (1)

7-9: 포트 계약이 간결하고 명확합니다.

src/main/java/starlight/application/backoffice/mail/required/BackofficeMailTemplateCommandPort.java (1)

5-9: 커맨드 포트 정의가 명확합니다.

src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateRepository.java (1)

6-7: 표준 JPA 리포지토리 사용이 적절합니다.

src/main/java/starlight/application/backoffice/mail/provided/BackofficeMailTemplateUseCase.java (1)

8-14: 유스케이스 계약이 명확합니다.

src/main/java/starlight/adapter/backoffice/mail/persistence/BackofficeMailTemplateJpa.java (1)

11-30: 포트-어댑터 위임 구조가 깔끔합니다.
Repository에만 책임을 두고 Command/Query 포트를 분리한 점이 좋습니다.

src/main/java/starlight/adapter/backoffice/mail/webapi/dto/response/BackofficeMailTemplateResponse.java (1)

7-25: 응답 DTO 매핑이 단순하고 명확합니다.
필드 매핑이 직관적이라 유지보수에 좋습니다.

src/main/java/starlight/application/backoffice/mail/BackofficeMailTemplateService.java (1)

46-51: null 입력 처리는 이미 충분히 검증됩니다

BackofficeMailContentType.from()은 이미 null 입력을 명시적으로 처리하며, IllegalArgumentException을 던집니다(line 15-17). 현재 parseContentType의 catch 블록이 이를 이미 BackofficeException으로 매핑하므로, 추가 null 가드는 불필요합니다.

Likely an incorrect or invalid review comment.

src/main/java/starlight/adapter/backoffice/mail/webapi/BackofficeMailController.java (4)

22-28: 요청 검증과 UseCase 위임 흐름이 깔끔합니다.
@Valid 요청 → 입력 변환 → UseCase 호출 → 성공 응답 흐름이 일관됩니다.


30-36: 템플릿 생성 응답 매핑이 명확합니다.
도메인 결과를 응답 DTO로 변환 후 ApiResponse로 래핑하는 흐름이 좋습니다.


38-43: 조회 매핑 로직이 간결합니다.
스트림 매핑과 toList() 사용이 깔끔합니다.


45-51: 삭제 API 처리 흐름이 단순합니다.
UseCase 호출 후 메시지 반환 패턴이 일관됩니다.

src/main/java/starlight/bootstrap/SecurityConfig.java (3)

64-85: 백오피스 전용 체인 분리와 권한 제한이 적절합니다.
메일 백오피스 경로에 대한 역할 기반 접근 제어가 명확합니다.


139-144: 백오피스 origin 추가 반영 확인했습니다.
허용 origin에 오피스 도메인을 포함해 CORS 차단 이슈를 줄였습니다.


173-181: AuthenticationProvider 명시 구성이 깔끔합니다.
UserDetailsService + PasswordEncoder 연결이 명확해 유지보수에 유리합니다.

src/main/java/starlight/application/backoffice/mail/BackofficeMailSendService.java (3)

49-55: 콘텐츠 타입 파싱/에러 매핑이 명확합니다.
잘못된 타입을 도메인 예외로 변환하는 흐름이 일관됩니다.


76-88: 실패 로그 이벤트 분리가 좋습니다.
실패 케이스를 한 곳에서 이벤트로 발행하도록 정리된 점이 좋습니다.


57-60:

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +16 to +26
@EventListener
public void handle(BackofficeMailSendEvent event) {
String recipients = String.join(",", event.to());
BackofficeMailSendLog log = BackofficeMailSendLog.create(
recipients,
event.subject(),
event.contentType(),
event.success(),
event.errorMessage()
);
logCommandPort.save(log);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Spring 비동기 이벤트 처리 설정 확인
echo "=== Async Configuration Search ==="
rg -n --type=java "ApplicationEventMulticaster|SimpleApplicationEventMulticaster|@EnableAsync|@Async" -A 2

# 2. BackofficeMailSendLogEventHandler 파일 찾기
echo -e "\n=== Finding BackofficeMailSendLogEventHandler ==="
fd -t f "BackofficeMailSendLogEventHandler"

# 3. 이벤트 리스너 패턴 확인
echo -e "\n=== All `@EventListener` usages ==="
rg -n "@EventListener" -A 3 --type=java

Repository: StartUpLight/STARLIGHT_BE

Length of output: 1510


🏁 Script executed:

#!/bin/bash
# AsyncConfig 전체 구현 확인
echo "=== AsyncConfig Implementation ==="
cat -n src/main/java/starlight/bootstrap/AsyncConfig.java

echo -e "\n=== BackofficeMailSendLogEventHandler Full Implementation ==="
cat -n src/main/java/starlight/application/backoffice/mail/BackofficeMailSendLogEventHandler.java

echo -e "\n=== FeedbackRequestEventListener for comparison ==="
cat -n src/main/java/starlight/application/expertApplication/event/FeedbackRequestEventListener.java

Repository: StartUpLight/STARLIGHT_BE

Length of output: 5715


@eventlistener는 기본적으로 동기 실행이라 로그 저장 예외가 메일 전송 흐름을 차단할 수 있음

현재 구현은 동기 @EventListener를 사용하고 있어 logCommandPort.save(log)에서 예외가 발생하면 caller로 직접 전파되어 메일 전송을 실패로 만들 수 있습니다.

같은 프로젝트의 FeedbackRequestEventListener에서 보이는 패턴을 따라 다음을 적용해주세요:

  • @Async("emailTaskExecutor") 추가로 비동기 실행
  • @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 추가로 트랜잭션 커밋 후 실행
  • 예외 처리 추가하여 로깅 실패가 메일 전송을 영향주지 않도록 격리
🤖 Prompt for AI Agents
In
`@src/main/java/starlight/application/backoffice/mail/BackofficeMailSendLogEventHandler.java`
around lines 16 - 26, The current BackofficeMailSendEventHandler.handle uses a
synchronous `@EventListener` and directly calls logCommandPort.save(log), which
can propagate exceptions back to the mail flow; change the handler to run
asynchronously and after transaction commit by annotating the handler with
`@Async`("emailTaskExecutor") and `@TransactionalEventListener`(phase =
TransactionPhase.AFTER_COMMIT) (replace or supplement the existing
`@EventListener`), and wrap the call to logCommandPort.save(log) in a try-catch
that logs any exception (without rethrowing) to ensure failures during
BackofficeMailSendLog.create/ logCommandPort.save do not affect mail sending;
reference symbols: BackofficeMailSendEvent,
BackofficeMailSendLogEventHandler.handle, BackofficeMailSendLog.create, and
logCommandPort.save.

Comment on lines +18 to +32
@Column(nullable = false, columnDefinition = "TEXT")
private String recipients;

@Column(nullable = false)
private String emailTitle;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private BackofficeMailContentType contentType;

@Column(nullable = false)
private boolean success;

@Column(columnDefinition = "TEXT")
private String errorMessage;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

수신자 이메일 저장은 개인정보 보관 정책 확인이 필요합니다.

recipients에 이메일 주소가 TEXT로 영구 저장됩니다. 로그 목적이라도 마스킹/암호화, 접근 권한, 보관 기간 및 삭제 정책이 명확하지 않으면 PII 리스크가 큽니다. 정책과 설계를 먼저 확정한 뒤 저장 방식을 결정해 주세요.

🤖 Prompt for AI Agents
In `@src/main/java/starlight/domain/backoffice/mail/BackofficeMailSendLog.java`
around lines 18 - 32, The recipients field in BackofficeMailSendLog is storing
raw email addresses as TEXT (recipients) which raises PII risk; before
persisting change the design: consult and codify the privacy/retention policy,
then implement one of the approved approaches (store masked emails, hash/encrypt
the recipients column, or store only a non-PII identifier) and add access
controls and retention/deletion logic; update the BackofficeMailSendLog model
(recipients) to reflect the chosen storage format and add clear
documentation/comments and any required encryption/decryption helper usage or
annotations so callers use the safe API.

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

🤖 Fix all issues with AI agents
In `@src/main/java/starlight/bootstrap/SecurityConfig.java`:
- Around line 69-73: The CSRF cookie domain is hardcoded in SecurityConfig;
update the code to externalize it via a configuration property injected with
`@Value` (e.g., add a private String csrfCookieDomain and annotate with
`@Value`("${security.csrf.cookie-domain:}") ), then use that variable inside
csrfTokenRepository.setCookieCustomizer(cookie ->
cookie.domain(csrfCookieDomain).sameSite("None").secure(true)); ensure
SecurityConfig handles an empty/null value (fallback to not setting domain or
using a sensible default) so dev/staging/prod can override via
application.properties or environment-specific config.
🧹 Nitpick comments (1)
src/main/java/starlight/bootstrap/SecurityConfig.java (1)

27-27: 사용되지 않는 import 존재

AntPathRequestMatcher가 import되었지만 현재 코드에서 사용되지 않습니다. IDE 또는 정적 분석 도구에서 경고가 발생할 수 있으니 확인 후 제거를 고려해 주세요.

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

🤖 Fix all issues with AI agents
In `@src/main/java/starlight/bootstrap/SecurityConfig.java`:
- Around line 93-107: The success handler in SecurityConfig.form.successHandler
currently builds a redirect URL from untrusted X-Forwarded-* headers (in the
lambda passed to form.successHandler), which allows open-redirects; change it to
always redirect to the configured officeBaseUrl (injected config property used
for backoffice base URL) instead of using headers, and ensure the code
normalizes/validates that officeBaseUrl is an absolute, trusted URL (append a
trailing "/" if missing) before calling response.sendRedirect; update the
handler to retrieve the injected officeBaseUrl (or throw/log if missing) and
call response.sendRedirect(officeBaseUrl) rather than constructing a URL from
request headers.
- Around line 82-86: In SecurityConfig's backofficeFilterChain configuration
remove the .ignoringRequestMatchers("/login", "/logout") call so CSRF protection
stays enabled for those endpoints, keep the existing csrfTokenRepository and
csrfTokenRequestHandler (they will continue to issue/validate tokens), ensure
your login form includes the CSRF token (CookieCsrfTokenRepository will make it
available) and keep logout implemented as a POST endpoint (do not exempt
"/logout"); update any documentation/tests that assumed CSRF was ignored for
login/logout.
- Around line 190-208: The current userDetailsService in SecurityConfig encodes
a plaintext backofficePassword at runtime (userDetailsService ->
password(passwordEncoder.encode(backofficePassword))), which is insecure; change
the code to accept and use a pre-bcrypted password property (e.g.
backofficePasswordHash) and pass it directly to User.builder().password(...)
without calling passwordEncoder.encode; keep the existing
PasswordEncoder/DaoAuthenticationProvider wiring (ensure the PasswordEncoder
bean is BCryptPasswordEncoder) so authentication verifies the bcrypt hash, and
update configuration/property names and docs to load the hashed secret from a
secrets store (Vault/Secrets Manager) for production.
♻️ Duplicate comments (1)
src/main/java/starlight/bootstrap/SecurityConfig.java (1)

69-77: CSRF 쿠키 도메인 하드코딩 이슈는 이전 리뷰와 동일

이 부분은 이전 리뷰에서 동일하게 지적된 사항과 중복됩니다.

@SeongHo5356 SeongHo5356 self-assigned this Jan 18, 2026
@SeongHo5356 SeongHo5356 requested a review from 2ghrms January 18, 2026 09:23
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: 2

🤖 Fix all issues with AI agents
In `@src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java`:
- Around line 43-46: The catch block in SmtpMailSender that catches
MessagingException currently logs the exception but throws BackofficeException
without preserving the cause; add a constructor on GlobalException that accepts
(ErrorType, Throwable) and calls super(message, cause), add a corresponding
constructor on BackofficeException that delegates to that GlobalException
constructor, then modify SmtpMailSender's catch (MessagingException e) to throw
new BackofficeException(BackofficeErrorType.MAIL_SEND_FAILED, e) so the original
MessagingException is preserved as the cause while keeping the existing log.

In
`@src/main/java/starlight/adapter/backoffice/mail/webapi/dto/request/BackofficeMailSendRequest.java`:
- Around line 22-34: The current isBodyProvided() allows unknown contentType
values to pass, letting invalid contentType like "invalid" slip through and
cause downstream exceptions; add a new validation method (e.g.,
isValidContentType()) on BackofficeMailSendRequest that returns true when
contentType is blank (letting `@NotBlank` handle that) or when contentType
equalsIgnoreCase "html" or "text", and annotate it with `@AssertTrue`(message =
"contentType must be 'html' or 'text'") so invalid values fail validation at the
API boundary; keep isBodyProvided() as-is but rely on the new
isValidContentType() to prevent unsupported contentType from reaching toInput()
/ BackofficeMailInput.of() / BackofficeMailContentType.from().
🧹 Nitpick comments (1)
src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java (1)

17-25: 테스트 용이성을 위해 생성자 주입 고려

@Value 필드 주입은 동작하지만, 단위 테스트 시 Spring 컨텍스트 없이 값을 주입하기 어렵습니다. 생성자 주입으로 변경하면 테스트에서 직접 값을 전달할 수 있습니다.

♻️ 생성자 주입으로 리팩터링 제안
 `@Slf4j`
 `@Service`
-@RequiredArgsConstructor
 public class SmtpMailSender implements MailSenderPort {

     private final JavaMailSender javaMailSender;
-
-    `@Value`("${spring.mail.username}")
-    private String senderEmail;
+    private final String senderEmail;
+
+    public SmtpMailSender(
+            JavaMailSender javaMailSender,
+            `@Value`("${spring.mail.username}") String senderEmail
+    ) {
+        this.javaMailSender = javaMailSender;
+        this.senderEmail = senderEmail;
+    }

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

🤖 Fix all issues with AI agents
In `@src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java`:
- Around line 41-45: The log currently prints full recipient emails in
SmtpMailSender (around the javaMailSender.send call), which exposes PII; update
the logging in the send method to avoid full addresses by logging only the
recipient count and a masked/obfuscated representation (e.g., mask local-part or
replace with “***”) instead of input.to(), while keeping subject via
input.subject(); ensure the log line referencing input.to() and input.subject()
is replaced with a safe variant and that the BackofficeException handling
remains unchanged.

Comment on lines +41 to +45
javaMailSender.send(message);
log.info("[MAIL] sent to={} subject={}", input.to(), input.subject());
} catch (MessagingException e) {
log.error("[MAIL] send failed to={}", input.to(), e);
throw new BackofficeException(BackofficeErrorType.MAIL_SEND_FAILED, e);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

수신자 이메일을 로그에 그대로 남기면 PII 리스크가 있습니다.
운영 로그에는 수신자 수/마스킹 값만 남기도록 조정해주세요.

🐛 제안 수정안
-            log.info("[MAIL] sent to={} subject={}", input.to(), input.subject());
+            log.info("[MAIL] sent recipients={} subject={}", input.to().size(), input.subject());
...
-            log.error("[MAIL] send failed to={}", input.to(), e);
+            log.error("[MAIL] send failed recipients={}", input.to().size(), e);
🤖 Prompt for AI Agents
In `@src/main/java/starlight/adapter/backoffice/mail/email/SmtpMailSender.java`
around lines 41 - 45, The log currently prints full recipient emails in
SmtpMailSender (around the javaMailSender.send call), which exposes PII; update
the logging in the send method to avoid full addresses by logging only the
recipient count and a masked/obfuscated representation (e.g., mask local-part or
replace with “***”) instead of input.to(), while keeping subject via
input.subject(); ensure the log line referencing input.to() and input.subject()
is replaced with a safe variant and that the BackofficeException handling
remains unchanged.

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.

2 participants