Skip to content

Commit

Permalink
Merge pull request #7809 from ita-social-projects/bugfix/fixing-messa…
Browse files Browse the repository at this point in the history
…ges-in-notifications

Fixing messages in notifications
  • Loading branch information
fedykvitalik2004 authored Nov 26, 2024
2 parents b74eb43 + 10ed898 commit bafb13d
Show file tree
Hide file tree
Showing 18 changed files with 637 additions and 151 deletions.
4 changes: 2 additions & 2 deletions dao/src/main/java/greencity/entity/Notification.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -62,7 +62,7 @@ public class Notification {
private boolean viewed;

@Column
private LocalDateTime time;
private ZonedDateTime time;

@Column
private boolean emailSent;
Expand Down
11 changes: 10 additions & 1 deletion dao/src/main/java/greencity/enums/NotificationType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package greencity.enums;

import java.util.EnumSet;

public enum NotificationType {
ECONEWS_COMMENT_REPLY,
ECONEWS_COMMENT_LIKE,
Expand Down Expand Up @@ -27,5 +29,12 @@ public enum NotificationType {
HABIT_COMMENT_USER_TAG,
HABIT_LAST_DAY_OF_PRIMARY_DURATION,
PLACE_STATUS,
PLACE_ADDED
PLACE_ADDED;

private static final EnumSet<NotificationType> COMMENT_LIKE_TYPES = EnumSet.of(
ECONEWS_COMMENT_LIKE, EVENT_COMMENT_LIKE, HABIT_COMMENT_LIKE);

public static boolean isCommentLike(final NotificationType notificationType) {
return COMMENT_LIKE_TYPES.contains(notificationType);
}
}
43 changes: 39 additions & 4 deletions dao/src/main/java/greencity/repository/NotificationRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
import greencity.enums.NotificationType;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.*;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -54,6 +51,27 @@ public interface NotificationRepo extends CustomNotificationRepo, JpaRepository<
Notification findNotificationByTargetUserIdAndNotificationTypeAndTargetId(Long targetUserId,
NotificationType notificationType, Long targetId);

/**
* Finds a {@link Notification} based on a unique combination of the target
* user's ID, the notification type, and an identifier that either matches the
* {@code secondMessageId} (if present) or the {@code targetId}. This query is
* designed to find a unique notification by the following criteria:
*
* @param targetUserId user, that should receive Notification
* @param notificationType type of Notification
* @param identifier identifier: either {@code secondMessageId} or
* {@code targetId}
* @return {@link Notification}
*/
@Query("SELECT n "
+ "FROM Notification AS n "
+ "WHERE n.targetUser.id = :targetUserId "
+ "AND n.notificationType = :notificationType "
+ "AND ((n.secondMessageId IS NOT NULL AND n.secondMessageId = :identifier) "
+ "OR (n.secondMessageId IS NULL AND n.targetId = :identifier))")
Notification findNotificationByTargetUserIdAndNotificationTypeAndIdentifier(Long targetUserId,
NotificationType notificationType, Long identifier);

/**
* Method to find specific not viewed Notification.
*
Expand Down Expand Up @@ -130,4 +148,21 @@ long countActionUsersByTargetUserIdAndNotificationTypeAndTargetIdAndViewedIsFals
* false otherwise
*/
boolean existsByIdAndTargetUserId(Long notificationId, Long targetUserId);

/**
* Finds an unviewed {@link Notification} by target user's ID, notification
* type, target ID, and second message ID. Returns a notification that matches
* the provided {@code targetUserId}, {@code notificationType},
* {@code targetId}, and {@code secondMessageId}, with the {@code viewed} flag
* set to {@code false}.
*
* @param targetUserId the ID of the target user.
* @param notificationType the type of the notification.
* @param targetId the identifier of the notification's target.
* @param secondMessageId the secondary message ID.
* @return an {@link Optional} containing the matching notification, or empty if
* not found.
*/
Optional<Notification> findByTargetUserIdAndNotificationTypeAndTargetIdAndViewedIsFalseAndSecondMessageId(
Long targetUserId, NotificationType notificationType, Long targetId, Long secondMessageId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@
<include file="db/changelog/logs/ch-add-table-eco-news-followers-Yehor.xml"/>
<include file="db/changelog/logs/ch-drop-column-expiry-date-Rozhko.xml"/>
<include file="db/changelog/logs/ch-rename-tables-shopping_list-to-to_do_list-Herchanivska-10.xml"/>
<include file="db/changelog/logs/ch-change-notification-time-Fedyk.xml"/>
<include file="db/changelog/logs/ch-add-habit-invite-table-Holotsvan.xml"/>
<include file="db/changelog/logs/ch-add-table-habit-followers-Holotsvan.xml"/>
<include file="db/changelog/logs/ch-events-users-dislike-table.xml"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
<changeSet author="Vitalii Fedyk" id="Fedyk-1">
<modifyDataType tableName="notifications" columnName="time" newDataType="timestamptz"/>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -40,7 +40,7 @@ public class EmailNotificationDto {

private boolean viewed;

private LocalDateTime time;
private ZonedDateTime time;

private boolean emailSent;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.List;

@NoArgsConstructor
Expand All @@ -19,7 +19,7 @@ public class NotificationDto {
private Long notificationId;
private String projectName;
private String notificationType;
private LocalDateTime time;
private ZonedDateTime time;
private Boolean viewed;

private String titleText;
Expand Down
11 changes: 10 additions & 1 deletion service-api/src/main/java/greencity/enums/NotificationType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package greencity.enums;

import java.util.EnumSet;

public enum NotificationType {
ECONEWS_COMMENT_REPLY,
ECONEWS_COMMENT_LIKE,
Expand Down Expand Up @@ -27,5 +29,12 @@ public enum NotificationType {
HABIT_COMMENT_USER_TAG,
HABIT_LAST_DAY_OF_PRIMARY_DURATION,
PLACE_STATUS,
PLACE_ADDED
PLACE_ADDED;

private static final EnumSet<NotificationType> COMMENT_LIKE_TYPES = EnumSet.of(
ECONEWS_COMMENT_LIKE, EVENT_COMMENT_LIKE, HABIT_COMMENT_LIKE);

public static boolean isCommentLike(final NotificationType notificationType) {
return COMMENT_LIKE_TYPES.contains(notificationType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,40 @@ void createNotification(UserVO targetUser, UserVO actionUser, NotificationType n
Long targetId, String customMessage);

/**
* Method to create Notification.
* Creates a notification for a target user. Notifications are uniquely
* identified by the combination of {@code targetUserId},
* {@code notificationType}, {@code targetId}, and {@code secondMessageId}.
*
* @param targetUser user, that should receive Notification
* @param actionUser user, that performed action
* @param notificationType type of Notification
* @param targetUser the user who will receive the notification
* @param actionUser the user who performed the action triggering the
* notification
* @param notificationType the type of notification to be created
* @param targetId represent the corresponding object's ID
* @param customMessage text of Notification, {message} in template
* @param secondMessageId if to secondMessageText
* @param secondMessageText additional text, {secondMessage} in template
* @author Volodymyr Mladonov
* @param customMessage a custom message for the notification
* @param secondMessageId a secondary identifier for additional context
* @param secondMessageText a secondary text for additional context
* @author Vitalii Fedyk
*/
void createNotification(UserVO targetUser, UserVO actionUser, NotificationType notificationType,
Long targetId, String customMessage, Long secondMessageId, String secondMessageText);

/**
* Creates a notification for a target user. Notifications are uniquely
* identified by the combination of {@code targetUserId},
* {@code notificationType}, and {@code targetId}.
*
* @param targetUser the user who will receive the notification
* @param actionUser the user who performed the action triggering the
* notification
* @param notificationType the type of notification to be created
* @param targetId represent the corresponding object's ID
* @param customMessage a custom message for the notification
* @param secondMessageText a secondary text for additional context
* @author Vitalii Fedyk
*/
void createNotification(UserVO targetUser, UserVO actionUser, NotificationType notificationType,
Long targetId, String customMessage, String secondMessageText);

/**
* Method to create Notification without actionUser.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package greencity.enums;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class NotificationTypeTest {
@ParameterizedTest
@EnumSource(names = {
"ECONEWS_COMMENT_LIKE",
"EVENT_COMMENT_LIKE",
"HABIT_COMMENT_LIKE"
})
void isCommentLike_ShouldReturnTrueForCommentLikeType(final NotificationType notificationType) {
assertTrue(NotificationType.isCommentLike(notificationType));
}

@ParameterizedTest
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = {
"ECONEWS_COMMENT_LIKE",
"EVENT_COMMENT_LIKE",
"HABIT_COMMENT_LIKE"
})
void isCommentLike_ShouldReturnFalseForCommentLikeType(final NotificationType notificationType) {
assertFalse(NotificationType.isCommentLike(notificationType));
}
}
65 changes: 24 additions & 41 deletions service/src/main/java/greencity/service/CommentServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ public AddCommentDtoResponse save(ArticleType articleType, Long articleId,
commentRepo.save(comment), AddCommentDtoResponse.class);
addCommentDtoResponse.setAuthor(modelMapper.map(userVO, CommentAuthorDto.class));
if (checkUserIsNotAuthor(userVO, articleAuthor) && !isCommentReply) {
createCommentNotification(articleType, articleId, comment, userVO, locale);
createCommentNotification(articleType, articleId, userVO, locale);
}
sendNotificationToTaggedUser(modelMapper.map(comment, CommentVO.class), articleType, locale);
sendNotificationToTaggedUser(comment, articleType, locale);

return addCommentDtoResponse;
}
Expand Down Expand Up @@ -181,23 +181,28 @@ private void addImagesToComment(Comment comment, MultipartFile[] images) {
/**
* Sends a notification to users tagged in a comment on a specific article.
*
* @param commentVO the comment containing the tag, {@link CommentVO}.
* @param articleType the type of the article where the comment is made,
* {@link ArticleType}.
* @param locale the locale used for localization of the notification,
* {@link Locale}.
* @throws NotFoundException if a tagged user is not found by ID.
*/
private void sendNotificationToTaggedUser(CommentVO commentVO, ArticleType articleType, Locale locale) {
String commentText = commentVO.getText();
private void sendNotificationToTaggedUser(Comment comment, ArticleType articleType, Locale locale) {
String commentText = comment.getText();
Set<Long> usersId = getUserIdFromComment(commentText);
NotificationType notificationType = getNotificationType(articleType, CommentActionType.COMMENT_USER_TAG);
CommentVO commentVO = modelMapper.map(comment, CommentVO.class);
if (!usersId.isEmpty()) {
for (Long userId : usersId) {
User user = userRepo.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND_BY_ID + userId));
createNotification(articleType, commentVO.getArticleId(), modelMapper.map(commentVO, Comment.class),
modelMapper.map(user, UserVO.class), commentVO.getUser(),
getNotificationType(articleType, CommentActionType.COMMENT_USER_TAG), locale);
userNotificationService.createNotification(
modelMapper.map(user, UserVO.class),
commentVO.getUser(),
notificationType,
commentVO.getArticleId(),
null,
getArticleTitle(articleType, commentVO.getArticleId(), locale));
}
}
}
Expand Down Expand Up @@ -265,32 +270,6 @@ protected String getArticleTitle(ArticleType articleType, Long articleId, Locale
return articleName;
}

/**
* Generic method for creating a notification for various comment-related
* actions on an article.
*
* @param articleType the type of the article, {@link ArticleType}.
* @param articleId the ID of the article, {@link Long}.
* @param comment the comment that triggered the notification,
* {@link Comment}.
* @param receiver the user receiving the notification, {@link UserVO}.
* @param sender the user sending the notification, {@link UserVO}.
* @param notificationType the type of notification, {@link NotificationType}.
* @throws BadRequestException if the article type is not supported.
*/
private void createNotification(ArticleType articleType, Long articleId, Comment comment, UserVO receiver,
UserVO sender, NotificationType notificationType, Locale locale) {
boolean hasParentComment = comment.getParentComment() != null;
userNotificationService.createNotification(
receiver,
sender,
notificationType,
articleId,
hasParentComment ? comment.getParentComment().getText() : comment.getText(),
hasParentComment ? comment.getParentComment().getId() : comment.getId(),
getArticleTitle(articleType, articleId, locale));
}

/**
* Determines the appropriate notification type based on article and action
* type.
Expand Down Expand Up @@ -331,16 +310,15 @@ private NotificationType getNotificationType(ArticleType articleType, CommentAct
*
* @param articleType the type of the article, {@link ArticleType}.
* @param articleId the ID of the article, {@link Long}.
* @param comment the comment that was made, {@link Comment}.
* @param userVO the user who made the comment, {@link UserVO}.
* @param locale the locale used for localization of the notification,
* {@link Locale}.
*/
private void createCommentNotification(ArticleType articleType, Long articleId, Comment comment, UserVO userVO,
Locale locale) {
private void createCommentNotification(ArticleType articleType, Long articleId, UserVO userVO, Locale locale) {
UserVO receiver = modelMapper.map(getArticleAuthor(articleType, articleId), UserVO.class);
String message = null;
ResourceBundle bundle = ResourceBundle.getBundle("notification", Locale.forLanguageTag(locale.getLanguage()),
ResourceBundle bundle = ResourceBundle.getBundle("notification",
Locale.forLanguageTag(locale.getLanguage()),
ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT));
long commentsCount = notificationRepo
.countActionUsersByTargetUserIdAndNotificationTypeAndTargetIdAndViewedIsFalse(receiver.getId(),
Expand All @@ -356,7 +334,6 @@ private void createCommentNotification(ArticleType articleType, Long articleId,
getNotificationType(articleType, CommentActionType.COMMENT),
articleId,
message,
comment.getId(),
getArticleTitle(articleType, articleId, locale));
}

Expand Down Expand Up @@ -397,8 +374,14 @@ private void createCommentLikeNotification(ArticleType articleType, Long article
*/
private void createCommentReplyNotification(ArticleType articleType, Long articleId, Comment comment,
UserVO sender, UserVO receiver, Locale locale) {
createNotification(articleType, articleId, comment, receiver,
sender, getNotificationType(articleType, CommentActionType.COMMENT_REPLY), locale);
userNotificationService.createNotification(
receiver,
sender,
getNotificationType(articleType, CommentActionType.COMMENT_REPLY),
articleId,
comment.getParentComment().getText(),
comment.getParentComment().getId(),
getArticleTitle(articleType, articleId, locale));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ private ScheduledEmailMessage createScheduledEmailMessage(Notification notificat
String subject = bundle.getString(notification.getNotificationType() + "_TITLE");
String bodyTemplate = bundle.getString(notification.getNotificationType().toString());
String actionUserText;
long actionUsersSize = notification.getActionUsers().size();
long actionUsersSize = notification.getActionUsers().stream().distinct().toList().size();
if (actionUsersSize > 1) {
actionUserText = actionUsersSize + " " + bundle.getString("USERS");
} else if (actionUsersSize == 1) {
Expand Down
Loading

0 comments on commit bafb13d

Please sign in to comment.