-
Notifications
You must be signed in to change notification settings - Fork 1
Refactor : FCM 리펙토링 및 PostController Mapping 관계 재설정 #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
a7e79e6
d9a2515
f468e1d
5456325
07ec9ee
49499c0
cf39b9d
010c36b
713dd10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,131 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package naughty.tuzamate.domain.pushToken; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.google.firebase.messaging.*; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class FcmSender { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 단일 토큰 전송: 전송/로깅만, DB 변경은 하지 않음 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public String sendToToken( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String token, String title, String body, Map<String, String> data, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| boolean highPriority, String clickAction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) throws Exception { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Message.Builder mb = Message.builder().setToken(token); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applyCommonNotification(mb, title, body); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applyCommonData(mb, data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mb.setAndroidConfig(buildAndroidConfig(highPriority, clickAction)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return FirebaseMessaging.getInstance().send(mb.build()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (FirebaseMessagingException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.warn("FCM single send failed token={}, code={}", token, e.getMessagingErrorCode(), e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw e; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mask tokens in logs (PII/sensitive). Avoid logging raw FCM tokens. Apply: - log.warn("FCM single send failed token={}, code={}", token, e.getMessagingErrorCode(), e);
+ log.warn("FCM single send failed token={}, code={}", maskToken(token), e.getMessagingErrorCode(), e);
@@
- log.warn("FCM multicast failed token={}, code={}", t, code, fme);
+ log.warn("FCM multicast failed token={}, code={}", maskToken(t), code, fme);
@@
- log.warn("FCM multicast failed token={} (non-Firebase exception)", t, ex);
+ log.warn("FCM multicast failed token={} (non-Firebase exception)", maskToken(t), ex);Add helper inside the class: private String maskToken(String t) {
if (t == null) return "null";
int n = t.length();
if (n <= 10) return "****" + t;
return t.substring(0, 4) + "..." + t.substring(n - 4);
}Also applies to: 72-82 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 다중 토큰 전송 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public BatchResult sendToTokens( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<String> tokens, String title, String body, Map<String, String> data, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| boolean highPriority, String clickAction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) throws Exception { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (tokens == null || tokens.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new BatchResult(0, 0, List.of()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int totalSuccess = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int totalFailure = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<String> permanentFailed = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (int start = 0; start < tokens.size(); start += 500) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<String> chunk = tokens.subList(start, Math.min(start + 500, tokens.size())); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filter null/blank tokens before chunking to avoid runtime errors.
- if (tokens == null || tokens.isEmpty()) {
+ if (tokens == null || tokens.isEmpty()) {
return new BatchResult(0, 0, List.of());
}
+ // filter invalid entries; keep order and duplicates if desired
+ tokens = tokens.stream()
+ .filter(t -> t != null && !t.isBlank())
+ .toList();
+ if (tokens.isEmpty()) {
+ return new BatchResult(0, 0, List.of());
+ }
+
int totalSuccess = 0;
int totalFailure = 0;
List<String> permanentFailed = new ArrayList<>();
- for (int start = 0; start < tokens.size(); start += 500) {
+ for (int start = 0; start < tokens.size(); start += 500) {
List<String> chunk = tokens.subList(start, Math.min(start + 500, tokens.size()));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MulticastMessage.Builder mb = MulticastMessage.builder().addAllTokens(chunk); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applyCommonNotification(mb, title, body); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| applyCommonData(mb, data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mb.setAndroidConfig(buildAndroidConfig(highPriority, clickAction)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast(mb.build()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| totalSuccess += response.getSuccessCount(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| totalFailure += response.getFailureCount(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<SendResponse> rs = response.getResponses(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (int i = 0; i < rs.size(); i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SendResponse r = rs.get(i); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!r.isSuccessful()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String t = chunk.get(i); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Exception ex = r.getException(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (ex instanceof FirebaseMessagingException fme) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MessagingErrorCode code = fme.getMessagingErrorCode(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.warn("FCM multicast failed token={}, code={}", t, code, fme); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 영구 실패만 수집 (일시 실패는 수집하지 않음) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isPermanentFailure(fme)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| permanentFailed.add(t); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (ex != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.warn("FCM multicast failed token={} (non-Firebase exception)", t, ex); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new BatchResult(totalSuccess, totalFailure, permanentFailed); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 영구 실패(비활성화 대상) 판정 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private boolean isPermanentFailure(FirebaseMessagingException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MessagingErrorCode c = e.getMessagingErrorCode(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return c == MessagingErrorCode.UNREGISTERED | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || c == MessagingErrorCode.INVALID_ARGUMENT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || c == MessagingErrorCode.SENDER_ID_MISMATCH | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || c == MessagingErrorCode.THIRD_PARTY_AUTH_ERROR; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ===== 공통 빌더 유틸 ===== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void applyCommonNotification(Message.Builder b, String title, String body) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (title != null || body != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| b.setNotification(Notification.builder().setTitle(title).setBody(body).build()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void applyCommonNotification(MulticastMessage.Builder b, String title, String body) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (title != null || body != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| b.setNotification(Notification.builder().setTitle(title).setBody(body).build()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void applyCommonData(Message.Builder b, Map<String, String> data) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (data != null && !data.isEmpty()) b.putAllData(data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private void applyCommonData(MulticastMessage.Builder b, Map<String, String> data) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (data != null && !data.isEmpty()) b.putAllData(data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private AndroidConfig buildAndroidConfig(boolean highPriority, String clickAction) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AndroidConfig.Builder ab = AndroidConfig.builder(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (highPriority) ab.setPriority(AndroidConfig.Priority.HIGH); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (clickAction != null && !clickAction.isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ab.setNotification(AndroidNotification.builder().setClickAction(clickAction).build()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ab.build(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 결과 DTO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public record BatchResult(int success, int failure, List<String> failedTokens) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,17 @@ | ||||||||||||||||||||||||||
| package naughty.tuzamate.domain.pushToken.code; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import lombok.AllArgsConstructor; | ||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||
| import naughty.tuzamate.global.error.BaseErrorCode; | ||||||||||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @AllArgsConstructor | ||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||
| public enum PushTokenErrorCode implements BaseErrorCode { | ||||||||||||||||||||||||||
| TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "TOKEN40401", "해당 토큰이 존재하지 않습니다."), | ||||||||||||||||||||||||||
| TOKEN_NOT_OWNER(HttpStatus.BAD_REQUEST, "TOKEN402", "토큰 소유자가 아닙니다."); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| private final HttpStatus status; | ||||||||||||||||||||||||||
| private final String code; | ||||||||||||||||||||||||||
| private final String message; | ||||||||||||||||||||||||||
|
Comment on lines
+11
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HTTP status/code mismatch for NOT_OWNER. Use 403 (Forbidden) and align the app code string with your 404 scheme. - TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "TOKEN40401", "해당 토큰이 존재하지 않습니다."),
- TOKEN_NOT_OWNER(HttpStatus.BAD_REQUEST, "TOKEN402", "토큰 소유자가 아닙니다.");
+ TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "TOKEN40401", "해당 토큰이 존재하지 않습니다."),
+ TOKEN_NOT_OWNER(HttpStatus.FORBIDDEN, "TOKEN40301", "토큰 소유자가 아닙니다.");📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,48 @@ | ||||||||||||||||||||||||||||
| package naughty.tuzamate.domain.pushToken.controller; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import jakarta.validation.Valid; | ||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||
| import naughty.tuzamate.domain.pushToken.FcmSender; | ||||||||||||||||||||||||||||
| import naughty.tuzamate.domain.pushToken.dto.PushTokenSendDTO; | ||||||||||||||||||||||||||||
| import naughty.tuzamate.global.apiPayload.CustomResponse; | ||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.RequestBody; | ||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.RestController; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // test 용 | ||||||||||||||||||||||||||||
| @RestController | ||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||
| @RequestMapping("/api/push") | ||||||||||||||||||||||||||||
| // @PreAuthorize("hasRole('ADMIN')") // 운영/관리자 전용 권장 | ||||||||||||||||||||||||||||
| public class PushMessageController { | ||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lock down “test” push endpoints. These endpoints can spam arbitrary tokens if exposed. Gate them. +import org.springframework.security.access.prepost.PreAuthorize;
@@
-// test 용
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/push")
-// @PreAuthorize("hasRole('ADMIN')") // 운영/관리자 전용 권장
+@PreAuthorize("hasRole('ADMIN')")
public class PushMessageController {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private final FcmSender fcmSender; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @PostMapping("/token") | ||||||||||||||||||||||||||||
| public CustomResponse<String> sendToToken(@Valid @RequestBody PushTokenSendDTO.SendToTokenRequest req) throws Exception { | ||||||||||||||||||||||||||||
| String messageId = fcmSender.sendToToken( | ||||||||||||||||||||||||||||
| req.token(), | ||||||||||||||||||||||||||||
| req.title(), | ||||||||||||||||||||||||||||
| req.body(), | ||||||||||||||||||||||||||||
| req.data(), | ||||||||||||||||||||||||||||
| Boolean.TRUE.equals(req.highPriority()), | ||||||||||||||||||||||||||||
| req.clickAction() | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| return CustomResponse.onSuccess(messageId); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @PostMapping("/tokens") | ||||||||||||||||||||||||||||
| public CustomResponse<FcmSender.BatchResult> sendToTokens(@Valid @RequestBody PushTokenSendDTO.SendToTokensRequest req) throws Exception { | ||||||||||||||||||||||||||||
| FcmSender.BatchResult result = fcmSender.sendToTokens( | ||||||||||||||||||||||||||||
| req.tokens(), | ||||||||||||||||||||||||||||
| req.title(), | ||||||||||||||||||||||||||||
| req.body(), | ||||||||||||||||||||||||||||
| req.data(), | ||||||||||||||||||||||||||||
| Boolean.TRUE.equals(req.highPriority()), | ||||||||||||||||||||||||||||
| req.clickAction() | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| return CustomResponse.onSuccess(result); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Narrow throws to FirebaseMessagingException.
Avoid
throws Exceptionin public API.Also applies to: 39-40
🤖 Prompt for AI Agents