Skip to content

Commit

Permalink
Development: Add health indicator api (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
PaRangger authored Jan 15, 2025
1 parent 1030c44 commit bf071f8
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.tum.cit.artemis.push.apns;

import com.eatthepath.pushy.apns.*;
import com.eatthepath.pushy.apns.server.RejectionReason;
import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder;
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture;
Expand All @@ -20,6 +21,7 @@
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.ExecutionException;

@Service
Expand All @@ -38,6 +40,8 @@ public class ApnsSendService implements SendService<NotificationRequest> {

private ApnsClient apnsClient;

private boolean isConnected;

@EventListener(ApplicationReadyEvent.class)
public void applicationReady() {
log.info("apnsCertificatePwd: {}", apnsCertificatePwd);
Expand All @@ -52,8 +56,10 @@ public void applicationReady() {
.setApnsServer(apnsProdEnvironment ? ApnsClientBuilder.PRODUCTION_APNS_HOST : ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
.setClientCredentials(new File(apnsCertificatePath), apnsCertificatePwd)
.build();
isConnected = true;
log.info("Started APNS client successfully!");
} catch (IOException e) {
isConnected = false;
log.error("Could not init APNS service", e);
}
}
Expand Down Expand Up @@ -94,10 +100,14 @@ private ResponseEntity<Void> sendApnsRequest(NotificationRequest request) {
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = responsePushNotificationFuture.get();
if (pushNotificationResponse.isAccepted()) {
log.info("Send notification to {}", request.token());
isConnected = true;
return ResponseEntity.ok().build();
} else {
var rejectionReasons = List.of(RejectionReason.BAD_CERTIFICATE.toString(), RejectionReason.BAD_CERTIFICATE_ENVIRONMENT.toString());
if(pushNotificationResponse.getRejectionReason().isPresent() && rejectionReasons.contains(pushNotificationResponse.getRejectionReason().get())) {
isConnected = false;
}
log.error("Notification rejected by the APNs gateway: {}", pushNotificationResponse.getRejectionReason());

pushNotificationResponse.getTokenInvalidationTimestamp().ifPresent(timestamp -> log.error("\t... and the token is invalid as of {}", timestamp));
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
}
Expand All @@ -106,4 +116,9 @@ private ResponseEntity<Void> sendApnsRequest(NotificationRequest request) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
}
}

@Override
public boolean isHealthy() {
return isConnected;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
public interface SendService<T> {

ResponseEntity<Void> send(T request);

boolean isHealthy();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.MessagingErrorCode;
import de.tum.cit.artemis.push.common.NotificationRequest;
import de.tum.cit.artemis.push.common.SendService;
import org.slf4j.Logger;
Expand All @@ -25,6 +26,8 @@ public class FirebaseSendService implements SendService<List<NotificationRequest

private Optional<FirebaseApp> firebaseApp = Optional.empty();

private boolean isConnected;

public FirebaseSendService() {
try {
FirebaseOptions options = FirebaseOptions
Expand All @@ -34,8 +37,11 @@ public FirebaseSendService() {
.build();

firebaseApp = Optional.of(FirebaseApp.initializeApp(options));

isConnected = true;
} catch (IOException e) {
log.error("Exception while loading Firebase credentials", e);
isConnected = false;
}
}

Expand All @@ -61,11 +67,24 @@ public ResponseEntity<Void> send(List<NotificationRequest> requests) {

try {
FirebaseMessaging.getInstance(firebaseApp.get()).sendEach(batch);
isConnected = true;
} catch (FirebaseMessagingException e) {
// In case the certificate is invalid, the THIRD_PARTY_AUTH_ERROR error code will be returned
var errorCodes = List.of(MessagingErrorCode.THIRD_PARTY_AUTH_ERROR);

if(errorCodes.contains(e.getMessagingErrorCode())) {
isConnected = false;
}

return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
}
}

return ResponseEntity.ok().build();
}

@Override
public boolean isHealthy() {
return isConnected;
}
}
4 changes: 4 additions & 0 deletions hermes/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ dependencies {
tasks.named('test') {
useJUnitPlatform()
}

springBoot {
buildInfo()
}
20 changes: 20 additions & 0 deletions hermes/src/main/java/de/tum/cit/artemis/push/HealthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.tum.cit.artemis.push;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HealthController {
private final NotificationHealthService healthService;

public HealthController(NotificationHealthService healthService) {
this.healthService = healthService;
}

@GetMapping("/health")
public HealthReport getHealth() {
return healthService.getHealthReport();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package de.tum.cit.artemis.push;

public record HealthReport(boolean isApnsConnected, boolean isFirebaseConnected, String versionNumber) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.tum.cit.artemis.push;

import de.tum.cit.artemis.push.apns.ApnsSendService;
import de.tum.cit.artemis.push.firebase.FirebaseSendService;
import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Service;

@Service
public class NotificationHealthService {
private final FirebaseSendService firebaseSendService;
private final ApnsSendService apnsSendService;
private final BuildProperties buildProperties;

NotificationHealthService(FirebaseSendService firebaseSendService, ApnsSendService apnsSendService, BuildProperties buildProperties) {
this.firebaseSendService = firebaseSendService;
this.apnsSendService = apnsSendService;
this.buildProperties = buildProperties;
}

public HealthReport getHealthReport() {
return new HealthReport(apnsSendService.isHealthy(), firebaseSendService.isHealthy(), buildProperties.getVersion());
}
}

0 comments on commit bf071f8

Please sign in to comment.