Notification Code.

This commit is contained in:
piyushkag
2024-12-13 20:57:58 +05:30
parent 2a5f344ea0
commit d8e51f3a70
25 changed files with 520 additions and 6 deletions

View File

@@ -23,7 +23,9 @@ public class TendermanagementApplication {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:3000")
//remove after testing
//add url for a demo html and js project created on Vs code and ran it from go live on right bottim corner user gepafin_dev_local backup DB for gettng notification on FE.
registry.addMapping("/**").allowedOrigins("http://127.0.0.1:5500", "http://localhost:3000", "http://localhost:5500")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD").allowCredentials(true);
}
}

View File

@@ -109,6 +109,7 @@ public class SecurityConfig {
.requestMatchers("/v1/api-docs/**").permitAll() // API docs
.requestMatchers("/v1/user/reset-password/initiate").permitAll()
.requestMatchers("/v1/user/reset-password").permitAll()
.requestMatchers("/wss/**").permitAll() // if this is not running use this /gs-guide-websocket
.anyRequest().authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.exceptionHandling(exceptionHandling -> exceptionHandling

View File

@@ -0,0 +1,32 @@
package net.gepafin.tendermanagement.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// Enable a simple broker for both /topic (broadcast messages) and /queue (user-specific messages)
config.enableStompBrokerRelay("/topic")
.setRelayHost("localhost")
.setRelayPort(61613) // RabbitMQ is running on port 61613
.setClientLogin("guest")
.setClientPasscode("guest");
// Prefix for application messages (user sends messages to /app endpoints)
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").setAllowedOrigins("http://127.0.0.1:5501/", "http://localhost:5500", "http://localhost:5501", "http://127.0.0.1:5500/")
.withSockJS();
}
}

View File

@@ -341,5 +341,9 @@ public class GepafinConstant {
public static final String POLLING_THREAD_NAME = "Ndg-Polling-Thread-";
public static final String DOCUMENT_UPLOADING_IN_PROGRESS = "document.uploading.is.in.progress";
public static final String ASYNC_DOCUMENT_UPLOAD_NAME = "AsyncDocumentUpload-";
//Notification
public static final String COMMON_SINGLE_CHANNEL_PREFIX = "/topic/notifications_user_";
public static final String COMPANY_PREFIX = "_company_";
}

View File

@@ -0,0 +1,5 @@
package net.gepafin.tendermanagement.constants;
public class NotificationConstant {
public static final String NOTIFICATION_SENT_SUCCESSFULLY = "Notification Sent Successfully.";
}

View File

@@ -15,7 +15,9 @@ import java.util.zip.ZipOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import net.gepafin.tendermanagement.entities.*;
import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum;
import net.gepafin.tendermanagement.enums.NotificationTypeEnum;
import net.gepafin.tendermanagement.enums.VersionActionTypeEnum;
import net.gepafin.tendermanagement.model.request.NotificationReq;
import net.gepafin.tendermanagement.model.request.VersionHistoryRequest;
import net.gepafin.tendermanagement.model.response.*;
import net.gepafin.tendermanagement.repositories.*;
@@ -108,6 +110,18 @@ public class CallDao {
@Autowired
private HttpServletRequest request;
@Autowired
private NotificationDao notificationDao;
@Autowired
private BeneficiaryRepository beneficiaryRepository;
@Autowired
private NotificationTypeRepository notificationTypeRepository;
@Autowired
private UserWithCompanyRepository userWithCompanyRepository;
public CallResponse createCallStep1(CreateCallRequestStep1 createCallRequest, UserEntity userEntity) {
createCallRequest.setRegionId(userEntity.getRoleEntity().getRegion().getId());
CallEntity callEntity = convertToCallEntity(createCallRequest, userEntity);
@@ -828,13 +842,36 @@ public class CallDao {
validateStatusChange(currentStatus, statusReq);
callEntity.setStatus(statusReq.getValue());
callEntity = callRepository.save(callEntity);
//Creating notification.
List<Long> userIds = beneficiaryRepository.findUserIdsByHubIdAndBeneficiaryId(callEntity.getHub().getId());
Map<String, String> placeholders = new HashMap<>();
placeholders.put("{{call_name}}", callEntity.getName());
userIds.forEach(userId -> {
NotificationReq notificationReq = createNotificationReq(NotificationTypeEnum.CALL_CREATED.getValue(), placeholders, userId);
List<Long> companyIds = userWithCompanyRepository.findActiveCompanyIdsByUserId(notificationReq.getUserId());
notificationReq.setCompanyIds(companyIds);
notificationDao.sendNotification(notificationReq);
});
/** This code is responsible for adding a version history log for the "update call status" operation **/
loggingUtil.addVersionHistory(VersionHistoryRequest.builder().request(request).actionType(VersionActionTypeEnum.UPDATE).oldData(oldCallEntity).newData(callEntity).build());
return convertToCallResponseBean(callEntity);
}
public NotificationReq createNotificationReq(String notificationType, Map<String, String> placeholders, Long userId) {
// Create NotificationReq object
NotificationReq notificationReq = new NotificationReq();
NotificationTypeEntity notificationTypeEntity = notificationTypeRepository.findByNotificationNameAndIsDeletedFalse(notificationType);
notificationReq.setNotificationType(notificationType);
String message = Utils.replacePlaceholders(notificationTypeEntity.getJsonTemplate(), placeholders);
notificationReq.setMessage(message);
notificationReq.setUserId(userId);
return notificationReq;
}
private void validateStatusChange(CallStatusEnum currentStatus, CallStatusEnum newStatus) {
if (currentStatus == newStatus) {
throw new CustomValidationException(Status.VALIDATION_ERROR,

View File

@@ -0,0 +1,97 @@
package net.gepafin.tendermanagement.dao;
import lombok.extern.slf4j.Slf4j;
import net.gepafin.tendermanagement.constants.GepafinConstant;
import net.gepafin.tendermanagement.entities.NotificationEntity;
import net.gepafin.tendermanagement.enums.NotificationEnum;
import net.gepafin.tendermanagement.model.request.NotificationReq;
import net.gepafin.tendermanagement.repositories.NotificationRepository;
import net.gepafin.tendermanagement.repositories.NotificationTypeRepository;
import net.gepafin.tendermanagement.util.Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@Slf4j
public class NotificationDao {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Autowired
private NotificationRepository notificationRepository;
@Autowired
private NotificationTypeRepository notificationTypeRepository;
public NotificationReq sendNotification(NotificationReq notificationReq) {
// Ensure userId is properly set in notificationReq if not already
Long userId = notificationReq.getUserId();
if (userId == null) {
log.error("User ID is missing in the notification request.");
return null;
}
NotificationEntity notificationEntity = saveNotification(notificationReq);
log.info("Sending notification to user {} with content: {}", userId, notificationReq.getMessage());
List<Long> companyIds = notificationReq.getCompanyIds();
if (companyIds.isEmpty()) {
sendToUser(userId, notificationEntity);
} else {
sendToCompanies(userId, companyIds, notificationEntity);
}
return convertToNotificationReq(notificationEntity);
}
private NotificationEntity saveNotification(NotificationReq notificationReq) {
NotificationEntity notificationEntity = convertToNotificationEntity(notificationReq);
return notificationRepository.save(notificationEntity);
}
private void sendToUser(Long userId, NotificationEntity notificationEntity) {
String userChannel = GepafinConstant.COMMON_SINGLE_CHANNEL_PREFIX + userId;
log.info("Channel for User {}", userChannel);
messagingTemplate.convertAndSend(userChannel, notificationEntity);
}
private void sendToCompanies(Long userId, List<Long> companyIds, NotificationEntity notificationEntity) {
// Send notification to each company provided in the companyIds list
companyIds.forEach(companyId -> {
String companyChannel = Utils.createChannelForUserAndCompany(userId, companyId);
log.info("Channel for User and Company {}, {}", userId, companyChannel);
messagingTemplate.convertAndSend(companyChannel, notificationEntity);
});
}
private NotificationReq convertToNotificationReq(NotificationEntity notificationEntity) {
NotificationReq notificationReq = new NotificationReq();
notificationReq.setId(notificationEntity.getId());
notificationReq.setUserId(notificationEntity.getUserId());
notificationReq.setStatus(NotificationEnum.UNREAD.getValue());
notificationReq.setMessage(notificationEntity.getMessage());
notificationReq.setCreatedDate(notificationEntity.getCreatedDate());
notificationReq.setUpdatedDate(notificationEntity.getUpdatedDate());
return notificationReq;
}
private NotificationEntity convertToNotificationEntity(NotificationReq notificationReq) {
NotificationEntity notificationEntity = new NotificationEntity();
String message = notificationReq.getMessage();
notificationEntity.setNotificationType(notificationReq.getNotificationType());
notificationEntity.setUserId(notificationReq.getUserId());
notificationEntity.setStatus(NotificationEnum.UNREAD.getValue());
notificationEntity.setIsDeleted(Boolean.FALSE);
notificationEntity.setMessage(message);
return notificationEntity;
}
}

View File

@@ -0,0 +1,31 @@
package net.gepafin.tendermanagement.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
@Entity
@Table(name = "NOTIFICATION")
@Data
public class NotificationEntity extends BaseEntity {
@Column(name = "USER_ID")
Long userId;
@Column(name = "MESSAGE")
String message;
@Column(name = "STATUS")
String status;
@Column(name = "IS_DELETED")
Boolean isDeleted;
@Column(name = "NOTIFICATION_TYPE")
String notificationType;
@Column(name = "REDIRECT_LINK")
String redirectLink;
}

View File

@@ -0,0 +1,21 @@
package net.gepafin.tendermanagement.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
@Entity
@Data
@Table(name = "NOTIFICATION_TYPE")
public class NotificationTypeEntity extends BaseEntity {
@Column(name = "NOTIFICATION_NAME")
String notificationName;
@Column(name = "JSON_TEMPLATE")
String jsonTemplate;
@Column(name="IS_DELETED")
private Boolean isDeleted;
}

View File

@@ -0,0 +1,27 @@
package net.gepafin.tendermanagement.enums;
import com.fasterxml.jackson.annotation.JsonValue;
public enum NotificationEnum {
//status
READ("READ"), UNREAD("UNREAD");
private final String value;
NotificationEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
}

View File

@@ -0,0 +1,29 @@
package net.gepafin.tendermanagement.enums;
import com.fasterxml.jackson.annotation.JsonValue;
public enum NotificationTypeEnum {
CALL_CREATED("CALL_CREATED"),
APPLICATION_SUBMISSION("APPLICATION_SUBMISSION"),
AMENDMENT_CREATION("AMENDMENT_CREATION"),
EVALUATION_RESULT("EVALUATION_RESULT");
private final String value;
NotificationTypeEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
}

View File

@@ -0,0 +1,37 @@
package net.gepafin.tendermanagement.model.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class NotificationReq {
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
Long id;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
Long userId;
String message;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
String notificationType;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
String status;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private LocalDateTime createdDate;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private LocalDateTime updatedDate;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
String redirectUrl;
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
List<Long> companyIds;
}

View File

@@ -40,6 +40,7 @@ public class UserResponseBean extends BaseBean {
private List<CompanyResponse> companies;
private Boolean privacy;
private Boolean terms;
private Boolean marketing;

View File

@@ -1,11 +1,16 @@
package net.gepafin.tendermanagement.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import net.gepafin.tendermanagement.entities.BeneficiaryEntity;
import java.util.List;
@Repository
public interface BeneficiaryRepository extends JpaRepository<BeneficiaryEntity, Long> {
@Query("SELECT u.id FROM UserEntity u JOIN u.beneficiary b WHERE b.id = u.beneficiary.id AND b.hubId = :hubId")
List<Long> findUserIdsByHubIdAndBeneficiaryId(Long hubId);
}

View File

@@ -0,0 +1,7 @@
package net.gepafin.tendermanagement.repositories;
import net.gepafin.tendermanagement.entities.NotificationEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NotificationRepository extends JpaRepository<NotificationEntity, Long> {
}

View File

@@ -0,0 +1,8 @@
package net.gepafin.tendermanagement.repositories;
import net.gepafin.tendermanagement.entities.NotificationTypeEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NotificationTypeRepository extends JpaRepository<NotificationTypeEntity, Long> {
NotificationTypeEntity findByNotificationNameAndIsDeletedFalse(String value);
}

View File

@@ -0,0 +1,7 @@
package net.gepafin.tendermanagement.service;
import net.gepafin.tendermanagement.model.request.NotificationReq;
public interface NotificationService {
NotificationReq sendNotification(Long userId, NotificationReq notificationReq);
}

View File

@@ -0,0 +1,26 @@
package net.gepafin.tendermanagement.service.impl;
import lombok.extern.slf4j.Slf4j;
import net.gepafin.tendermanagement.dao.NotificationDao;
import net.gepafin.tendermanagement.model.request.NotificationReq;
import net.gepafin.tendermanagement.service.NotificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class NotificationServiceImpl implements NotificationService {
@Autowired
private NotificationDao notificationDao;
@Override
public NotificationReq sendNotification(Long userId, NotificationReq notificationReq) {
log.info("Sending notification to user {} with content: {}", userId, notificationReq.getMessage());
notificationReq.setUserId(userId);
notificationReq = notificationDao.sendNotification(notificationReq);
return notificationReq;
}
}

View File

@@ -669,4 +669,8 @@ public class Utils {
}
return null;
}
public static String createChannelForUserAndCompany(Long userId, Long companyId) {
return GepafinConstant.COMMON_SINGLE_CHANNEL_PREFIX + userId + GepafinConstant.COMPANY_PREFIX + companyId;
}
}

View File

@@ -0,0 +1,30 @@
package net.gepafin.tendermanagement.web.rest.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import net.gepafin.tendermanagement.model.request.NotificationReq;
import net.gepafin.tendermanagement.model.response.LookUpDataResponseBean;
import net.gepafin.tendermanagement.model.util.Response;
import net.gepafin.tendermanagement.web.rest.api.errors.ErrorConstants;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public interface NotificationApi {
@Operation(summary = "Api to send notification.", responses = { @ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = @ExampleObject(value =
ErrorConstants.NOTFOUND_ERROR_EXAMPLE))),
@ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = @ExampleObject(value =
ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE))),
@ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = @ExampleObject(value =
ErrorConstants.BADREQUEST_ERROR_EXAMPLE))) })
@PostMapping(value = "/{userId}", consumes = "application/json", produces = "application/json")
ResponseEntity<Response<NotificationReq>> sendNotification(HttpServletRequest request, @RequestBody NotificationReq notificationReq,
@Parameter(description = "The user id", required = true) @PathVariable("userId") Long userId);
}

View File

@@ -0,0 +1,32 @@
package net.gepafin.tendermanagement.web.rest.api.impl;
import jakarta.servlet.http.HttpServletRequest;
import net.gepafin.tendermanagement.config.Translator;
import net.gepafin.tendermanagement.constants.NotificationConstant;
import net.gepafin.tendermanagement.model.request.NotificationReq;
import net.gepafin.tendermanagement.model.util.Response;
import net.gepafin.tendermanagement.service.NotificationService;
import net.gepafin.tendermanagement.web.rest.api.NotificationApi;
import net.gepafin.tendermanagement.web.rest.api.errors.Status;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("${openapi.gepafin.base-path:/v1/notification}")
public class NotificationApiController implements NotificationApi {
@Autowired
private NotificationService notificationService;
public ResponseEntity<Response<NotificationReq>> sendNotification(HttpServletRequest request, NotificationReq notificationReq, Long userId) {
NotificationReq notificationData = notificationService.sendNotification(userId, notificationReq);
return ResponseEntity.status(HttpStatus.OK)
.body(new Response<>(notificationData, Status.SUCCESS, Translator.toLocale(NotificationConstant.NOTIFICATION_SENT_SUCCESSFULLY)));
}
}