diff --git a/push-notification-api/build.gradle b/push-notification-api/build.gradle new file mode 100644 index 0000000..5ad0848 --- /dev/null +++ b/push-notification-api/build.gradle @@ -0,0 +1,63 @@ +plugins { + id "org.ec4j.editorconfig" version "0.0.3" + id "checkstyle" +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +compileJava.options.encoding = "UTF-8" +compileTestJava.options.encoding = "UTF-8" + +idea { + module { + outputDir file("build/classes/main") + testOutputDir file("build/classes/test") + } +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +ext { + FIREBASE_ADMIN_VERSION = "6.8.1" + OKHTTP_VERSION = "4.2.2" +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.google.firebase:firebase-admin:${FIREBASE_ADMIN_VERSION}") + implementation("com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}") + + compileOnly("org.projectlombok:lombok") + annotationProcessor("org.projectlombok:lombok") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + + testCompileOnly("org.projectlombok:lombok") + testAnnotationProcessor("org.projectlombok:lombok") +} + +test { + useJUnitPlatform() +} + +editorconfig { + excludes = ["build"] +} + +check.dependsOn editorconfigCheck + +checkstyle { + configFile = file("${project.rootDir}/rule/naver-checkstyle-rules.xml") + configProperties = [config_loc: ".."] + toolVersion = "8.24" + ignoreFailures = false + maxErrors = 0 + maxWarnings = 0 +} diff --git a/push-notification-api/src/main/java/com/bluebird/pipit/fcm/controller/PushNotificationController.java b/push-notification-api/src/main/java/com/bluebird/pipit/fcm/controller/PushNotificationController.java new file mode 100644 index 0000000..ddca29c --- /dev/null +++ b/push-notification-api/src/main/java/com/bluebird/pipit/fcm/controller/PushNotificationController.java @@ -0,0 +1,17 @@ +package com.bluebird.pipit.fcm.controller; + +import com.bluebird.pipit.fcm.dto.PushNotificationRequest; +import com.bluebird.pipit.fcm.service.PushNotificationService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class PushNotificationController { + private final PushNotificationService pushNotificationService; + + public void sendMessage(@RequestBody PushNotificationRequest request) { + pushNotificationService.sendMessageTo(request.getTargetToken(), request.getTitle(), request.getBody()); + } +} diff --git a/push-notification-api/src/main/java/com/bluebird/pipit/fcm/domain/PushNotificationMessage.java b/push-notification-api/src/main/java/com/bluebird/pipit/fcm/domain/PushNotificationMessage.java new file mode 100644 index 0000000..53b8660 --- /dev/null +++ b/push-notification-api/src/main/java/com/bluebird/pipit/fcm/domain/PushNotificationMessage.java @@ -0,0 +1,30 @@ +package com.bluebird.pipit.fcm.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@AllArgsConstructor +public class PushNotificationMessage { + private boolean validate_only; + private Message message; + + @Builder + @Getter + @AllArgsConstructor + public static class Message { + private Notification notification; + private String token; + } + + @Builder + @Getter + @AllArgsConstructor + public static class Notification { + private String title; + private String body; + private String image; + } +} diff --git a/push-notification-api/src/main/java/com/bluebird/pipit/fcm/dto/PushNotificationRequest.java b/push-notification-api/src/main/java/com/bluebird/pipit/fcm/dto/PushNotificationRequest.java new file mode 100644 index 0000000..319963e --- /dev/null +++ b/push-notification-api/src/main/java/com/bluebird/pipit/fcm/dto/PushNotificationRequest.java @@ -0,0 +1,10 @@ +package com.bluebird.pipit.fcm.dto; + +import lombok.Value; + +@Value +public class PushNotificationRequest { + String targetToken; + String title; + String body; +} diff --git a/push-notification-api/src/main/java/com/bluebird/pipit/fcm/service/PushNotificationService.java b/push-notification-api/src/main/java/com/bluebird/pipit/fcm/service/PushNotificationService.java new file mode 100644 index 0000000..22854f8 --- /dev/null +++ b/push-notification-api/src/main/java/com/bluebird/pipit/fcm/service/PushNotificationService.java @@ -0,0 +1,92 @@ +package com.bluebird.pipit.fcm.service; + +import com.bluebird.pipit.fcm.domain.PushNotificationMessage; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.auth.oauth2.GoogleCredentials; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.http.HttpHeaders; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PushNotificationService { + private static final String FIREBASE_CONFIG_PATH = "firebase/firebase_service_key.json"; // TODO : add firebase project service key file + private static final String API_URL = "https://fcm.googleapis.com/v1/projects/{firebase-project-id}/messages:send"; + private static final String OAUTH_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; + + private final ObjectMapper objectMapper; + + + private String getAccessToken() { + GoogleCredentials googleCredentials; + try { + googleCredentials = GoogleCredentials + .fromStream(new ClassPathResource(FIREBASE_CONFIG_PATH).getInputStream()) + .createScoped(List.of(OAUTH_SCOPE)); + } catch (IOException e) { + throw new RuntimeException("Failed to open Firebase Configuration File.", e); + } + + try { + googleCredentials.refreshIfExpired(); + } catch (IOException e) { + log.error("Failed to refresh Google Credentials.", e); + } + + return googleCredentials.getAccessToken().getTokenValue(); + } + + private String makeMessage(String targetToken, String title, String body) throws JsonProcessingException { + PushNotificationMessage message = PushNotificationMessage.builder() + .message( + PushNotificationMessage.Message.builder() + .token(targetToken) + .notification( + PushNotificationMessage.Notification.builder() + .title(title) + .body(body) + .image(null) + .build() + ).build()) + .validate_only(false) + .build(); + + return objectMapper.writeValueAsString(message); + + } + + public void sendMessageTo(String targetToken, String title, String body) { + String message; + try { + message = makeMessage(targetToken, title, body); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to make Message.", e); + } + + 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()) + .build(); + + try { + client.newCall(request).execute(); + } catch (IOException e) { + log.error("Failed to send request.", e); + } + } + + + +} diff --git a/settings.gradle b/settings.gradle index 2bfa263..edfabdd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,3 +2,5 @@ rootProject.name = 'pipit' include 'pipit-api' include 'authentication-api' include 'support' +include 'push-notification-api' +