Skip to content

Commit

Permalink
Hotfix substitutes - notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
PAException committed Nov 1, 2023
1 parent 7401247 commit f24f492
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,15 @@ public class SubstituteController {
private final SubstituteRepository substituteRepository;
private final NotificationService notificationService;


/**
* Checks if a string is not blank, empty or null.
*
* @param value string to check
* @return true if given string is not blank or null
*/
private static boolean notBlank(String value) {
return value != null && !value.isBlank();
}

/**
* Create a {@link SubstituteModel} out of a {@link SubstituteDTO}.
*
* @param substituteId id of substitute
* @param dto with information
* @return created substitute model
*/
private static SubstituteModel createSubstitute(int substituteId, SubstituteDTO dto) {
private static SubstituteModel createSubstitute(SubstituteDTO dto) {
return new SubstituteModel(
substituteId,
-1,
dto.getDate(),
dto.getClassName(),
dto.getLesson(),
Expand Down Expand Up @@ -90,10 +78,11 @@ public void updateSubstitutes(List<SubstituteDTO> fetchedDTOs, Date date) {
List<SubstituteDTO> updated = new ArrayList<>(), created = new ArrayList<>();
List<SubstituteModel> toSave = new ArrayList<>();
for (SubstituteDTO dto: fetchedDTOs) {
if (current.contains(dto)) updated.add(dto);
else created.add(dto);
if (current.stream().anyMatch(substituteDTO -> substituteDTO.sameBase(dto))) {
if (!current.contains(dto)) updated.add(dto);
} else created.add(dto);

toSave.add(createSubstitute(-1, dto));
toSave.add(createSubstitute(dto));
}

this.substituteRepository.saveAll(toSave);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,7 @@ public SubstituteDTO appendText(String text) {
return this;
}

@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;

SubstituteDTO dto = (SubstituteDTO) other;
public boolean sameBase(SubstituteDTO dto) {
if (!Objects.equals(date, dto.date)) return false;
if (lesson != dto.lesson) return false;
if (!Objects.equals(className, dto.className)) return false;
Expand All @@ -60,8 +55,16 @@ public boolean equals(Object other) {
return true;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SubstituteDTO that = (SubstituteDTO) o;
return lesson == that.lesson && Objects.equals(date, that.date) && Objects.equals(className, that.className) && Objects.equals(subject, that.subject) && Objects.equals(substituteTeacher, that.substituteTeacher) && Objects.equals(teacher, that.teacher) && Objects.equals(type, that.type) && Objects.equals(substituteOf, that.substituteOf) && Objects.equals(room, that.room) && Objects.equals(text, that.text);
}

@Override
public int hashCode() {
return Objects.hash(date, className, lesson, teacher);
return Objects.hash(date, className, lesson, subject, substituteTeacher, teacher, type, substituteOf, room, text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
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 com.google.firebase.messaging.MulticastMessage;
import com.google.firebase.messaging.Notification;
import com.google.firebase.messaging.SendResponse;
import io.github.paexception.engelsburg.api.util.Environment;
import io.github.paexception.engelsburg.api.util.LoggingComponent;
import lombok.AccessLevel;
Expand All @@ -23,6 +25,7 @@
import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -89,9 +92,9 @@ public void sendNotificationToTopics(String title, String body, Map<String, Stri
* @param body of notification
* @param data of notification
* @param tokens devices to send notification to
* @return batchResponse of send notifications
* @return list of token that failed
*/
public BatchResponse sendMulticastNotification(String title, String body,
public List<String> sendMulticastNotification(String title, String body,
List<String> tokens, Map<String, String> data) throws FirebaseMessagingException {
MulticastMessage multicastMessage = MulticastMessage.builder()
.addAllTokens(tokens)
Expand All @@ -101,7 +104,21 @@ public BatchResponse sendMulticastNotification(String title, String body,
.setBody(body)
.build())
.build();
return FirebaseMessaging.getInstance().sendMulticast(multicastMessage, !Environment.PRODUCTION);

BatchResponse responses = FirebaseMessaging.getInstance().sendMulticast(multicastMessage, !Environment.PRODUCTION);

//Get all tokens that failed
List<String> invalidTokens = new ArrayList<>();
List<SendResponse> responsesResponses = responses.getResponses();
for (int i = 0; i < responsesResponses.size(); i++) {
SendResponse response = responsesResponses.get(i);
if (response.isSuccessful()) continue;

if (response.getException().getMessagingErrorCode().equals(MessagingErrorCode.UNREGISTERED))
invalidTokens.add(tokens.get(i));
}

return invalidTokens;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@

package io.github.paexception.engelsburg.api.service.notification;

import com.google.firebase.messaging.BatchResponse;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.MessagingErrorCode;
import com.google.firebase.messaging.SendResponse;
import io.github.paexception.engelsburg.api.controller.reserved.NotificationSettingsController;
import io.github.paexception.engelsburg.api.endpoint.dto.ArticleDTO;
import io.github.paexception.engelsburg.api.endpoint.dto.SubstituteDTO;
import io.github.paexception.engelsburg.api.endpoint.dto.response.SubstituteNotificationDTO;
import io.github.paexception.engelsburg.api.util.LoggingComponent;
import io.github.paexception.engelsburg.api.util.l10n.Localization;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -40,7 +36,7 @@ public class NotificationService implements LoggingComponent {
* Splits possible className merges like 10ab to 10a, 10b to send notifications to specific topics.
*
* @param className to split
* @return array of classNames
* @return list of classNames
*/
private static List<String> splitClasses(String className) {
if (className.length() <= 2 || (Character.isDigit(className.charAt(1)) && className.length() == 3)) {
Expand Down Expand Up @@ -83,10 +79,9 @@ private static List<String> splitClasses(String className) {
* Return a formatted text to display substitutes.
*
* @param substitute substitute to format
* @param langCode language code
* @return formatted text
*/
private static String getSubstituteText(@NotNull SubstituteNotificationDTO substitute, @NotNull String langCode) {
private static String getSubstituteText(@NotNull SubstituteNotificationDTO substitute) {
return (substitute.getClassName() == null ? "" : substitute.getClassName()) +
(substitute.getClassName() == null ? "" : " – ") +
(substitute.getSubject() == null ? "" : substitute.getSubject()) + " (" +
Expand All @@ -102,7 +97,7 @@ private static String getSubstituteText(@NotNull SubstituteNotificationDTO subst
!substitute.getSubstituteTeacher().equals("+") &&
substitute.getTeacher() != null &&
!substitute.getSubstituteTeacher().equals(substitute.getTeacher())
? " " + Localization.string(langCode, "insteadOf") + " "
? " statt "
: "") +
(substitute.getTeacher() == null || substitute.getTeacher().equals(substitute.getSubstituteTeacher())
? ""
Expand All @@ -120,27 +115,21 @@ private static String getSubstituteText(@NotNull SubstituteNotificationDTO subst
*
* @param substitute substitute to format
* @param created if substitute was updated or created
* @param langCode language abbreviation
* @return title
*/
private static String getSubstituteTitle(SubstituteNotificationDTO substitute, boolean created, String langCode) {
return (!created ? Localization.string(langCode, "changed") + ": " : "")
private static String getSubstituteTitle(SubstituteNotificationDTO substitute, boolean created) {
return (!created ? "Geändert" + ": " : "")
+ substitute.getLesson() + " " + substitute.getType();
}

/**
* Processes SubstituteDTOs to send as notification.
* Combines same substitutes.
* E.g. if 5th and 6th lesson are equal.
*
* @param dtos SubstituteDTOs
* @param created if given substitutes have been created
* @param dtos raw substitute data
* @return the combined dtos ready to send
*/
@Async
public void sendSubstituteNotifications(List<SubstituteDTO> dtos, boolean created) {
final String langCode = "de_DE";
LOGGER.debug(
"[Notification] Starting to send " + dtos.size() + " substitute notification" + (dtos.size() != 1 ? "s" : ""));

//Remove same substitutes (e.g. 5th and 6th lesson)
private static List<SubstituteNotificationDTO> combineDTOs(List<SubstituteDTO> dtos) {
List<SubstituteNotificationDTO> notificationDTOs = new ArrayList<>();
for (int i = 0; i < dtos.size(); i++) {
List<Integer> same = new ArrayList<>();
Expand Down Expand Up @@ -185,68 +174,78 @@ public void sendSubstituteNotifications(List<SubstituteDTO> dtos, boolean create
notificationDTOs.add(SubstituteNotificationDTO.fromSubstituteDTO(dto, null));
}
}
Map<String, String> data = Map.of("link", "/substitutes");

//Send general substitute notifications if they have been created
if (created) {
FCM.sendNotificationToTopics(
Localization.string(langCode, "newSubstitute").placeholder("count", notificationDTOs.size()).get(),
null,
data,
"substitute"
);
}
return notificationDTOs;
}

/**
* Processes SubstituteDTOs to send as notification.
*
* @param dtos SubstituteDTOs
* @param created if given substitutes have been created
*/
@Async
public void sendSubstituteNotifications(List<SubstituteDTO> dtos, boolean created) {
LOGGER.debug(
"[NOTIFICATION] Starting to send " + dtos.size() + " substitute notification" + (dtos.size() != 1 ? "s" : ""));

//Remove same substitutes (e.g. 5th and 6th lesson)
List<SubstituteNotificationDTO> notificationDTOs = combineDTOs(dtos);
Map<String, String> data = Map.of("link", "/substitutes");

//Send substitute notifications to topics (classes and teacher)
for (SubstituteNotificationDTO dto : notificationDTOs) {
Set<String> tokens = new HashSet<>();
try {
//Send general substitute notifications if they have been created
if (created) {
List<String> tokens = notificationSettingsController.getTokensOf("substitute");

//Classes
if (dto.getClassName() != null) {
for (String className : splitClasses(dto.getClassName().toUpperCase()))
tokens.addAll(this.notificationSettingsController.getTokensOf("substitute.class." + className));
}
List<String> failed = FCM.sendMulticastNotification(
notificationDTOs.size() + " Vertretung(en) geändert oder veröffentlicht!",
null,
tokens,
data
);

//Teacher
if (dto.getTeacher() != null) {
String teacher = "substitute.teacher." + dto.getTeacher().toUpperCase();
tokens.addAll(this.notificationSettingsController.getTokensOf(teacher));
//Delete all tokens that failed
this.notificationSettingsController.deleteInvalidTokens(failed);
}

//Timetable
tokens.addAll(this.notificationSettingsController.getTimetableTokens(dto));

//Send notification to all tokens
if (!tokens.isEmpty()) {
try {
List<String> tokenList = new ArrayList<>(tokens);
BatchResponse responses = FCM.sendMulticastNotification(
getSubstituteTitle(dto, created, langCode),
getSubstituteText(dto, langCode),
tokenList,
data
);
//Send substitute notifications to topics (classes and teacher)
for (SubstituteNotificationDTO dto : notificationDTOs) {
Set<String> tokens = new HashSet<>();

//Get all tokens that failed
List<String> invalidTokens = new ArrayList<>();
List<SendResponse> responsesResponses = responses.getResponses();
for (int i = 0; i < responsesResponses.size(); i++) {
SendResponse response = responsesResponses.get(i);
if (response.isSuccessful()) continue;
//Classes
if (dto.getClassName() != null) {
for (String className : splitClasses(dto.getClassName().toUpperCase()))
tokens.addAll(this.notificationSettingsController.getTokensOf("substitute.class." + className));
}

if (response.getException().getMessagingErrorCode().equals(MessagingErrorCode.UNREGISTERED))
invalidTokens.add(tokenList.get(i));
}
//Teacher
if (dto.getTeacher() != null) {
String teacher = "substitute.teacher." + dto.getTeacher().toUpperCase();
tokens.addAll(this.notificationSettingsController.getTokensOf(teacher));
}

//Timetable
tokens.addAll(this.notificationSettingsController.getTimetableTokens(dto));

//Send notification to all tokens
if (!tokens.isEmpty()) {
List<String> failed = FCM.sendMulticastNotification(
getSubstituteTitle(dto, created),
getSubstituteText(dto),
new ArrayList<>(tokens),
data
);

//Delete all tokens that failed
this.notificationSettingsController.deleteInvalidTokens(invalidTokens);
} catch (FirebaseMessagingException e) {
this.logError("[Notification] Couldn't send notifications", e, LOGGER);
this.notificationSettingsController.deleteInvalidTokens(failed);
}
}
} catch (FirebaseMessagingException e) {
this.logError("[NOTIFICATION] Couldn't send notifications", e, LOGGER);
}
LOGGER.info("[Notification] Sent " + dtos.size() + " substitute notification" + (dtos.size() != 1 ? "s" : ""));
LOGGER.info("[NOTIFICATION] Sent " + dtos.size() + " substitute notification" + (dtos.size() != 1 ? "s" : ""));
}

/**
Expand All @@ -255,13 +254,22 @@ public void sendSubstituteNotifications(List<SubstituteDTO> dtos, boolean create
* @param dto ArticleDTO
*/
public void sendArticleNotifications(ArticleDTO dto) {
LOGGER.info("[Notification] Sending article notifications (articleId: " + dto.getArticleId() + ")");
FCM.sendNotificationToTopics(
Localization.string("de_DE", "newArticle").get(),
dto.getTitle(),
Map.of("link", "/article"),
"article"
);
LOGGER.info("[NOTIFICATION] Sending article notifications (articleId: " + dto.getArticleId() + ")");
List<String> tokens = notificationSettingsController.getTokensOf("article");

try {
List<String> failed = FCM.sendMulticastNotification(
"Neuer Artikel veröffentlicht!",
dto.getTitle(),
tokens,
Map.of("link", "/article")
);

//Delete all tokens that failed
this.notificationSettingsController.deleteInvalidTokens(failed);
} catch (FirebaseMessagingException e) {
this.logError("[NOTIFICATION] Couldn't send notifications", e, LOGGER);
}
}

}

0 comments on commit f24f492

Please sign in to comment.