diff --git a/build.gradle b/build.gradle index caf2b71..7e75d98 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,11 @@ dependencies { implementation 'com.auth0:java-jwt:3.18.2' implementation 'com.auth0:jwks-rsa:0.20.0' + //fcm + implementation 'com.google.firebase:firebase-admin:6.8.1' + implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.2.2' + implementation 'com.google.guava:guava:30.0-jre' + //s3 bucket implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.1000') implementation 'com.amazonaws:aws-java-sdk-s3' diff --git a/src/main/java/com/aidiary/domain/auth/application/AuthService.java b/src/main/java/com/aidiary/domain/auth/application/AuthService.java index 8ccb18b..88a863d 100644 --- a/src/main/java/com/aidiary/domain/auth/application/AuthService.java +++ b/src/main/java/com/aidiary/domain/auth/application/AuthService.java @@ -65,4 +65,11 @@ public NicknameRes updateNickname(UserPrincipal userPrincipal, String nickname) return nicknameRes; } + + @Transactional + public void updateFcmToken(Long id, String fcmToken) { + User user = userRepository.findById(id) + .orElseThrow(EntityNotFoundException::new); + user.updateFcmToken(fcmToken); + } } diff --git a/src/main/java/com/aidiary/domain/auth/dto/IdTokenReq.java b/src/main/java/com/aidiary/domain/auth/dto/IdTokenReq.java index 61f9788..8074e3d 100644 --- a/src/main/java/com/aidiary/domain/auth/dto/IdTokenReq.java +++ b/src/main/java/com/aidiary/domain/auth/dto/IdTokenReq.java @@ -6,6 +6,7 @@ @Builder public record IdTokenReq( String idToken, - String provider + String provider, + String fcmToken ) { } diff --git a/src/main/java/com/aidiary/domain/auth/presentation/AuthController.java b/src/main/java/com/aidiary/domain/auth/presentation/AuthController.java index 850801b..3daabb4 100644 --- a/src/main/java/com/aidiary/domain/auth/presentation/AuthController.java +++ b/src/main/java/com/aidiary/domain/auth/presentation/AuthController.java @@ -62,6 +62,8 @@ public ResponseCustom login(@RequestBody IdTokenReq idTokenReq) { User user = userRepository.findByProviderId(providerId) .orElseThrow(EntityNotFoundException::new); + authService.updateFcmToken(user.getId(), idTokenReq.fcmToken()); + AuthRes authRes = AuthRes.builder() .accessToken(accessToken) .isRegistered(user.isRegistered()) diff --git a/src/main/java/com/aidiary/domain/notification/application/FcmService.java b/src/main/java/com/aidiary/domain/notification/application/FcmService.java new file mode 100644 index 0000000..bb2cb38 --- /dev/null +++ b/src/main/java/com/aidiary/domain/notification/application/FcmService.java @@ -0,0 +1,71 @@ +package com.aidiary.domain.notification.application; + +import com.aidiary.domain.notification.dto.FcmMessage; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.auth.oauth2.GoogleCredentials; +import lombok.RequiredArgsConstructor; +import okhttp3.*; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class FcmService { + + private final String API_URL = "https://fcm.googleapis.com/v1/projects/" + + "nabi-ffce7/messages:send"; + private final ObjectMapper objectMapper; + + @Transactional + public void sendMessageTo(String targetToken, String title, String body) throws IOException { + String message = makeMessage(targetToken, title, body); + + OkHttpClient client = new OkHttpClient(); + RequestBody requestBody = RequestBody.create(message, + MediaType.get("application/json; charset=utf-8")); + Request request = new Request.Builder() + .url(API_URL) + .post(requestBody) + .addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken()) + .addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8") + .build(); + + Response response = client.newCall(request).execute(); + + System.out.println(response.body().string()); + } + + private String makeMessage(String targetToken, String title, String body) throws JsonParseException, JsonProcessingException { + FcmMessage fcmMessage = FcmMessage.builder() + .message(FcmMessage.Msg.builder() + .token(targetToken) + .notification(FcmMessage.Notification.builder() + .title(title) + .body(body) + .image(null) + .build() + ).build()).validateOnly(false).build(); + + return objectMapper.writeValueAsString(fcmMessage); + } + + private String getAccessToken() throws IOException { + String firebaseConfigPath = "firebase/firebase_service_key.json"; + + GoogleCredentials googleCredentials = GoogleCredentials + .fromStream(new ClassPathResource(firebaseConfigPath).getInputStream()) + .createScoped(List.of("https://www.googleapis.com/auth/cloud-platform")); + + googleCredentials.refreshIfExpired(); + return googleCredentials.getAccessToken().getTokenValue(); + } + +} diff --git a/src/main/java/com/aidiary/domain/notification/dto/FcmMessage.java b/src/main/java/com/aidiary/domain/notification/dto/FcmMessage.java new file mode 100644 index 0000000..aac8a50 --- /dev/null +++ b/src/main/java/com/aidiary/domain/notification/dto/FcmMessage.java @@ -0,0 +1,28 @@ +package com.aidiary.domain.notification.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +public record FcmMessage( + boolean validateOnly, + Msg message +) { + @Builder + @AllArgsConstructor + @Getter + public static class Msg { + private Notification notification; + private String token; + } + + @Builder + @AllArgsConstructor + @Getter + public static class Notification { + private String title; + private String body; + private String image; + } +} diff --git a/src/main/java/com/aidiary/domain/notification/dto/FcmReq.java b/src/main/java/com/aidiary/domain/notification/dto/FcmReq.java new file mode 100644 index 0000000..72ac0eb --- /dev/null +++ b/src/main/java/com/aidiary/domain/notification/dto/FcmReq.java @@ -0,0 +1,8 @@ +package com.aidiary.domain.notification.dto; + +public record FcmReq( + String targetToken, + String title, + String body +) { +} diff --git a/src/main/java/com/aidiary/domain/notification/presentation/NotificationController.java b/src/main/java/com/aidiary/domain/notification/presentation/NotificationController.java new file mode 100644 index 0000000..fa6b5a6 --- /dev/null +++ b/src/main/java/com/aidiary/domain/notification/presentation/NotificationController.java @@ -0,0 +1,35 @@ +package com.aidiary.domain.notification.presentation; + +import com.aidiary.domain.notification.application.FcmService; +import com.aidiary.domain.notification.dto.FcmReq; +import com.aidiary.global.payload.ResponseCustom; +import lombok.RequiredArgsConstructor; +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; + +import java.io.IOException; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/fcm") +public class NotificationController { + + private final FcmService fcmService; + + + @PostMapping + public ResponseCustom pushMessage(@RequestBody FcmReq fcmReq) throws IOException { + System.out.println(fcmReq.targetToken() + " " + + fcmReq.title() + " " + fcmReq.body()); + + fcmService.sendMessageTo( + fcmReq.targetToken(), + fcmReq.title(), + fcmReq.body() + ); + + return ResponseCustom.OK("Message is pushed"); + } +} diff --git a/src/main/java/com/aidiary/domain/user/domain/User.java b/src/main/java/com/aidiary/domain/user/domain/User.java index 95b1db0..92ccb45 100644 --- a/src/main/java/com/aidiary/domain/user/domain/User.java +++ b/src/main/java/com/aidiary/domain/user/domain/User.java @@ -30,6 +30,8 @@ public class User extends BaseEntity { private boolean isRegistered; + private String fcmToken; + @Builder public User(String nickname, String username, String email, String role, String provider, String providerId) { @@ -49,4 +51,8 @@ public void updateNickname(String nickname) { public void updateIsRegistered() { this.isRegistered = true; } + + public void updateFcmToken(String fcmToken) { + this.fcmToken = fcmToken; + } }