diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000..8f7e8aa
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1 @@
+lombok.addLombokGeneratedAnnotation = true
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 4607d9c..54f915b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,7 @@
Notification service for rbc library
17
+ 1.4.2.Final
productdock
https://sonarcloud.io
ProductDock_rbc-library-notification
@@ -24,6 +25,19 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
org.projectlombok
@@ -39,6 +53,48 @@
spring-boot-starter-test
test
+
+ org.springframework.kafka
+ spring-kafka-test
+ 2.6.3
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.17.0
+ test
+
+
+ org.testcontainers
+ kafka
+ 1.15.3
+ test
+
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+
+ de.flapdoodle.embed
+ de.flapdoodle.embed.mongo
+ test
+
+
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+
diff --git a/src/main/java/com/productdock/library/notification/NotificationApplication.java b/src/main/java/com/productdock/library/notification/NotificationApplication.java
index 06cb599..1cc2f60 100644
--- a/src/main/java/com/productdock/library/notification/NotificationApplication.java
+++ b/src/main/java/com/productdock/library/notification/NotificationApplication.java
@@ -1,9 +1,15 @@
package com.productdock.library.notification;
import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+import org.springframework.kafka.annotation.EnableKafka;
+@EnableKafka
@SpringBootApplication
+@EnableAutoConfiguration
+@EnableMongoRepositories
public class NotificationApplication {
public static void main(String[] args) {
diff --git a/src/main/java/com/productdock/library/notification/adapter/in/kafka/KafkaConsumer.java b/src/main/java/com/productdock/library/notification/adapter/in/kafka/KafkaConsumer.java
new file mode 100644
index 0000000..2c1ab1b
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/in/kafka/KafkaConsumer.java
@@ -0,0 +1,34 @@
+package com.productdock.library.notification.adapter.in.kafka;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.productdock.library.notification.adapter.in.kafka.messages.NotificationMapper;
+import com.productdock.library.notification.adapter.in.kafka.messages.NotificationMessage;
+import com.productdock.library.notification.application.port.in.AddNotificationUseCase;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.stereotype.Component;
+
+import java.time.OffsetDateTime;
+
+@Component
+@Slf4j
+@AllArgsConstructor
+public class KafkaConsumer {
+
+ private NotificationMapper notificationMapper;
+ private ObjectMapper objectMapper;
+ private AddNotificationUseCase addNotificationUseCase;
+
+ @KafkaListener(topics = "${spring.kafka.topic.notifications}")
+ public synchronized void listenNotifications(ConsumerRecord message) throws JsonProcessingException {
+ log.info("Received notification kafka message: {}", message);
+
+ var notificationMessage = objectMapper.readValue(message.value(), NotificationMessage.class);
+ var notification = notificationMapper.toDomain(notificationMessage);
+ notification.setCreatedDate(OffsetDateTime.now());
+ addNotificationUseCase.saveNotification(notification);
+ }
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/ActionMessage.java b/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/ActionMessage.java
new file mode 100644
index 0000000..dee1e7b
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/ActionMessage.java
@@ -0,0 +1,13 @@
+package com.productdock.library.notification.adapter.in.kafka.messages;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+@Builder
+public class ActionMessage {
+ private String type;
+ private String target;
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMapper.java b/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMapper.java
new file mode 100644
index 0000000..c8543ac
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMapper.java
@@ -0,0 +1,15 @@
+package com.productdock.library.notification.adapter.in.kafka.messages;
+
+import com.productdock.library.notification.domain.Notification;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.ReportingPolicy;
+
+@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
+public interface NotificationMapper {
+
+ @Mapping(target = "read", ignore = true)
+ @Mapping(target = "id", ignore = true)
+ @Mapping(target = "createdDate", ignore = true)
+ public Notification toDomain(NotificationMessage source);
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMessage.java b/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMessage.java
new file mode 100644
index 0000000..ee44bed
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMessage.java
@@ -0,0 +1,13 @@
+package com.productdock.library.notification.adapter.in.kafka.messages;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+
+@Builder
+@AllArgsConstructor
+public class NotificationMessage {
+ public final String title;
+ public final String description;
+ public final String userId;
+ public final ActionMessage action;
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/in/web/NotificationsApi.java b/src/main/java/com/productdock/library/notification/adapter/in/web/NotificationsApi.java
new file mode 100644
index 0000000..006688d
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/in/web/NotificationsApi.java
@@ -0,0 +1,46 @@
+package com.productdock.library.notification.adapter.in.web;
+
+import com.productdock.library.notification.application.port.in.GetNotificationsQuery;
+import com.productdock.library.notification.application.port.in.ReadNotificationsUseCase;
+import com.productdock.library.notification.domain.Notification;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("api/notifications")
+@Slf4j
+@AllArgsConstructor
+public class NotificationsApi {
+
+ private GetNotificationsQuery getNotificationsQuery;
+ private ReadNotificationsUseCase readNotificationsUseCase;
+
+ public static final String CLAIM_EMAIL = "email";
+
+ @GetMapping
+ public List getNotifications(Authentication authentication) {
+ var userId = getUserId(authentication);
+ return getNotificationsQuery.getNotifications(userId);
+ }
+
+ @GetMapping("/unread")
+ public int getUnreadCount(Authentication authentication){
+ var userId = getUserId(authentication);
+ return getNotificationsQuery.getUnreadNotificationsCount(userId);
+ }
+
+ @PostMapping("/read")
+ public void readNotifications(Authentication authentication) {
+ var userId = getUserId(authentication);
+ readNotificationsUseCase.markAsReadNotifications(userId);
+ }
+
+ private String getUserId(Authentication authentication) {
+ return ((Jwt) authentication.getCredentials()).getClaim(CLAIM_EMAIL).toString();
+ }
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/out/mongo/NotificationPersistenceAdapter.java b/src/main/java/com/productdock/library/notification/adapter/out/mongo/NotificationPersistenceAdapter.java
new file mode 100644
index 0000000..6d2ebe4
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/out/mongo/NotificationPersistenceAdapter.java
@@ -0,0 +1,47 @@
+package com.productdock.library.notification.adapter.out.mongo;
+
+import com.productdock.library.notification.adapter.out.mongo.enitity.NotificationEntity;
+import com.productdock.library.notification.adapter.out.mongo.mapper.NotificationEntityMapper;
+import com.productdock.library.notification.application.port.out.persistence.NotificationPersistenceOutPort;
+import com.productdock.library.notification.domain.Notification;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+@Slf4j
+@AllArgsConstructor
+public class NotificationPersistenceAdapter implements NotificationPersistenceOutPort {
+
+ private NotificationRepository notificationRepository;
+ private NotificationEntityMapper mapper;
+ private MongoTemplate mongoTemplate;
+
+ @Override
+ public List getAllByUserId(String userId) {
+ return notificationRepository.findAllByUserId(userId)
+ .stream().map(notificationEntity -> mapper.toDomain(notificationEntity)).toList();
+ }
+
+ @Override
+ public List getUnreadByUserId(String userId) {
+ return notificationRepository.findAllByReadAndUserId(false, userId)
+ .stream().map(notificationEntity -> mapper.toDomain(notificationEntity)).toList();
+ }
+
+ @Override
+ public void save(Notification notification) {
+ log.info("Notification for saving: {}", notification);
+ var saved = notificationRepository.save(mapper.toEntity(notification));
+ List savedNotification = notificationRepository.findAllByUserId(notification.getUserId());
+ log.info("Saved notification: {}", savedNotification);
+ }
+
+ @Override
+ public void saveAll(List notifications) {
+ notificationRepository.saveAll(notifications.stream().map(notification -> mapper.toEntity(notification)).toList());
+ }
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/out/mongo/NotificationRepository.java b/src/main/java/com/productdock/library/notification/adapter/out/mongo/NotificationRepository.java
new file mode 100644
index 0000000..103d277
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/out/mongo/NotificationRepository.java
@@ -0,0 +1,15 @@
+package com.productdock.library.notification.adapter.out.mongo;
+
+import com.productdock.library.notification.adapter.out.mongo.enitity.NotificationEntity;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface NotificationRepository extends MongoRepository {
+
+ List findAllByUserId(String userId);
+
+ List findAllByReadAndUserId(boolean read, String userId);
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/out/mongo/enitity/ActionEntity.java b/src/main/java/com/productdock/library/notification/adapter/out/mongo/enitity/ActionEntity.java
new file mode 100644
index 0000000..711d248
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/out/mongo/enitity/ActionEntity.java
@@ -0,0 +1,17 @@
+package com.productdock.library.notification.adapter.out.mongo.enitity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ActionEntity implements Serializable {
+ private String type;
+ private String target;
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/out/mongo/enitity/NotificationEntity.java b/src/main/java/com/productdock/library/notification/adapter/out/mongo/enitity/NotificationEntity.java
new file mode 100644
index 0000000..d85ddf2
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/out/mongo/enitity/NotificationEntity.java
@@ -0,0 +1,26 @@
+package com.productdock.library.notification.adapter.out.mongo.enitity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.io.Serializable;
+
+@Document("notifications")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class NotificationEntity implements Serializable {
+ @Id
+ private String id;
+ private String title;
+ private String description;
+ private String userId;
+ private boolean read;
+ private String createdDate;
+ private ActionEntity action;
+}
diff --git a/src/main/java/com/productdock/library/notification/adapter/out/mongo/mapper/NotificationEntityMapper.java b/src/main/java/com/productdock/library/notification/adapter/out/mongo/mapper/NotificationEntityMapper.java
new file mode 100644
index 0000000..33dc5d6
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/adapter/out/mongo/mapper/NotificationEntityMapper.java
@@ -0,0 +1,22 @@
+package com.productdock.library.notification.adapter.out.mongo.mapper;
+
+import com.productdock.library.notification.adapter.out.mongo.enitity.NotificationEntity;
+import com.productdock.library.notification.domain.Notification;
+import org.mapstruct.Mapper;
+import org.mapstruct.ReportingPolicy;
+
+import java.time.OffsetDateTime;
+
+@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
+public interface NotificationEntityMapper {
+ NotificationEntity toEntity(Notification source);
+ Notification toDomain(NotificationEntity source);
+
+ default String map(OffsetDateTime value){
+ return value.toString();
+ }
+
+ default OffsetDateTime map(String value) {
+ return OffsetDateTime.parse(value);
+ }
+}
diff --git a/src/main/java/com/productdock/library/notification/application/port/in/AddNotificationUseCase.java b/src/main/java/com/productdock/library/notification/application/port/in/AddNotificationUseCase.java
new file mode 100644
index 0000000..8b7bf99
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/application/port/in/AddNotificationUseCase.java
@@ -0,0 +1,8 @@
+package com.productdock.library.notification.application.port.in;
+
+import com.productdock.library.notification.domain.Notification;
+
+public interface AddNotificationUseCase {
+
+ void saveNotification(Notification notification);
+}
diff --git a/src/main/java/com/productdock/library/notification/application/port/in/GetNotificationsQuery.java b/src/main/java/com/productdock/library/notification/application/port/in/GetNotificationsQuery.java
new file mode 100644
index 0000000..4603fe8
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/application/port/in/GetNotificationsQuery.java
@@ -0,0 +1,12 @@
+package com.productdock.library.notification.application.port.in;
+
+import com.productdock.library.notification.domain.Notification;
+
+import java.util.List;
+
+public interface GetNotificationsQuery {
+
+ List getNotifications(String userId);
+
+ int getUnreadNotificationsCount(String userId);
+}
diff --git a/src/main/java/com/productdock/library/notification/application/port/in/ReadNotificationsUseCase.java b/src/main/java/com/productdock/library/notification/application/port/in/ReadNotificationsUseCase.java
new file mode 100644
index 0000000..a192da9
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/application/port/in/ReadNotificationsUseCase.java
@@ -0,0 +1,6 @@
+package com.productdock.library.notification.application.port.in;
+
+public interface ReadNotificationsUseCase {
+
+ void markAsReadNotifications(String userId);
+}
diff --git a/src/main/java/com/productdock/library/notification/application/port/out/persistence/NotificationPersistenceOutPort.java b/src/main/java/com/productdock/library/notification/application/port/out/persistence/NotificationPersistenceOutPort.java
new file mode 100644
index 0000000..e352d9e
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/application/port/out/persistence/NotificationPersistenceOutPort.java
@@ -0,0 +1,16 @@
+package com.productdock.library.notification.application.port.out.persistence;
+
+import com.productdock.library.notification.domain.Notification;
+
+import java.util.List;
+
+public interface NotificationPersistenceOutPort {
+
+ List getAllByUserId(String userId);
+
+ List getUnreadByUserId(String userId);
+
+ void save(Notification notification);
+
+ void saveAll(List notifications);
+}
diff --git a/src/main/java/com/productdock/library/notification/application/service/AddNotificationService.java b/src/main/java/com/productdock/library/notification/application/service/AddNotificationService.java
new file mode 100644
index 0000000..f6cb836
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/application/service/AddNotificationService.java
@@ -0,0 +1,18 @@
+package com.productdock.library.notification.application.service;
+
+import com.productdock.library.notification.application.port.in.AddNotificationUseCase;
+import com.productdock.library.notification.application.port.out.persistence.NotificationPersistenceOutPort;
+import com.productdock.library.notification.domain.Notification;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+public class AddNotificationService implements AddNotificationUseCase {
+
+ private NotificationPersistenceOutPort notificationPersistenceOutPort;
+ @Override
+ public void saveNotification(Notification notification) {
+ notificationPersistenceOutPort.save(notification);
+ }
+}
diff --git a/src/main/java/com/productdock/library/notification/application/service/GetNotificationsService.java b/src/main/java/com/productdock/library/notification/application/service/GetNotificationsService.java
new file mode 100644
index 0000000..089c118
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/application/service/GetNotificationsService.java
@@ -0,0 +1,27 @@
+package com.productdock.library.notification.application.service;
+
+import com.productdock.library.notification.application.port.in.GetNotificationsQuery;
+import com.productdock.library.notification.application.port.out.persistence.NotificationPersistenceOutPort;
+import com.productdock.library.notification.domain.Notification;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+@AllArgsConstructor
+@Slf4j
+public class GetNotificationsService implements GetNotificationsQuery {
+
+ private NotificationPersistenceOutPort notificationPersistenceOutPort;
+ @Override
+ public List getNotifications(String userId) {
+ return notificationPersistenceOutPort.getAllByUserId(userId);
+ }
+
+ @Override
+ public int getUnreadNotificationsCount(String userId) {
+ return notificationPersistenceOutPort.getUnreadByUserId(userId).size();
+ }
+}
diff --git a/src/main/java/com/productdock/library/notification/application/service/ReadNotificationsService.java b/src/main/java/com/productdock/library/notification/application/service/ReadNotificationsService.java
new file mode 100644
index 0000000..1e9d433
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/application/service/ReadNotificationsService.java
@@ -0,0 +1,23 @@
+package com.productdock.library.notification.application.service;
+
+import com.productdock.library.notification.application.port.in.ReadNotificationsUseCase;
+import com.productdock.library.notification.application.port.out.persistence.NotificationPersistenceOutPort;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.stereotype.Service;
+
+@Service
+@AllArgsConstructor
+@Slf4j
+public class ReadNotificationsService implements ReadNotificationsUseCase {
+
+ private NotificationPersistenceOutPort notificationPersistenceOutPort;
+
+ @Override
+ public void markAsReadNotifications(String userId) {
+ var notifications = notificationPersistenceOutPort.getUnreadByUserId(userId);
+ notifications.forEach(notification -> notification.setRead(true));
+ notificationPersistenceOutPort.saveAll(notifications);
+ }
+}
diff --git a/src/main/java/com/productdock/library/notification/config/SecurityConfig.java b/src/main/java/com/productdock/library/notification/config/SecurityConfig.java
new file mode 100644
index 0000000..690a298
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/config/SecurityConfig.java
@@ -0,0 +1,34 @@
+package com.productdock.library.notification.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+
+import java.security.interfaces.RSAPublicKey;
+
+@Configuration
+public class SecurityConfig {
+
+ @Value("${jwt.public.key}")
+ RSAPublicKey key;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http.authorizeRequests(authorize -> authorize.antMatchers("/actuator/**").permitAll().anyRequest().authenticated())
+ .cors().and()
+ .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
+ return http.build();
+ }
+
+ @Bean
+ JwtDecoder jwtDecoder() {
+ return NimbusJwtDecoder.withPublicKey(this.key).build();
+ }
+}
diff --git a/src/main/java/com/productdock/library/notification/domain/Action.java b/src/main/java/com/productdock/library/notification/domain/Action.java
new file mode 100644
index 0000000..a870e7d
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/domain/Action.java
@@ -0,0 +1,13 @@
+package com.productdock.library.notification.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+@Builder
+public class Action {
+ private String type;
+ private String target;
+}
diff --git a/src/main/java/com/productdock/library/notification/domain/Notification.java b/src/main/java/com/productdock/library/notification/domain/Notification.java
new file mode 100644
index 0000000..61ae753
--- /dev/null
+++ b/src/main/java/com/productdock/library/notification/domain/Notification.java
@@ -0,0 +1,20 @@
+package com.productdock.library.notification.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+import java.time.OffsetDateTime;
+
+@Data
+@Builder
+@AllArgsConstructor
+public class Notification {
+ private String id;
+ private String title;
+ private String description;
+ private String userId;
+ private boolean read;
+ private OffsetDateTime createdDate;
+ private Action action;
+}
diff --git a/src/main/resources/app-local.pub b/src/main/resources/app-local.pub
new file mode 100644
index 0000000..0b2ee7b
--- /dev/null
+++ b/src/main/resources/app-local.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd
+7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv
+c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6
+iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2
+kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o
+RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj
+KwIDAQAB
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml
new file mode 100644
index 0000000..c2af6a7
--- /dev/null
+++ b/src/main/resources/application-local.yaml
@@ -0,0 +1,8 @@
+KAFKA_SERVER_URL: localhost:9093
+USER_PROFILES_JWT_PUBLIC_KEY: src/main/resources/app-local.pub
+MONGO_SERVER_URL: localhost
+MONGO_SERVER_PORT: 28017
+MONGO_USERNAME: root
+MONGO_PASSWORD: root1
+
+LOG_TO_FILE: false
\ No newline at end of file
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 18386cc..7eb1695 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -1,12 +1,48 @@
+spring:
+ kafka:
+ enabled: true
+ bootstrap-servers: ${KAFKA_SERVER_URL}
+ value-seriliazer: org.apache.kafka.common.serialization.StringSerializer
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ producer:
+ client-id: kafka-message-producer
+ acks: all
+ value-seriliazer: org.apache.kafka.common.serialization.StringSerializer
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ consumer:
+ enable-auto-commit: true
+ auto-offset-reset: earliest
+ group-id: notifications-group
+ topic:
+ notifications: notifications
+
+ data:
+ mongodb:
+ authentication-database: notification
+ host: ${MONGO_SERVER_URL}
+ port: ${MONGO_SERVER_PORT}
+ username: ${MONGO_USERNAME}
+ password: ${MONGO_PASSWORD}
+ database: notification
+
+logging:
+ level:
+ com.productdock.library.notification: INFO
+
+file-logging-enabled: ${LOG_TO_FILE}
+
+jwt:
+ public.key: file:${USER_PROFILES_JWT_PUBLIC_KEY}
+
server:
- port: 8080
+ port: 8086
management:
server:
- port: 8087
+ port: 8099
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
- include: '*'
+ include: '*'
\ No newline at end of file
diff --git a/src/test/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMapperShould.java b/src/test/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMapperShould.java
new file mode 100644
index 0000000..a8de572
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/adapter/in/kafka/messages/NotificationMapperShould.java
@@ -0,0 +1,33 @@
+package com.productdock.library.notification.adapter.in.kafka.messages;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static com.productdock.library.notification.data.provider.in.kafka.NotificationMessageMother.notificationMessage;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {NotificationMapperImpl.class})
+class NotificationMapperShould {
+
+ @Autowired
+ private NotificationMapper notificationMapper;
+
+ @Test
+ void mapMessageToDomain() {
+ var notificationMessage = notificationMessage();
+
+ var notification = notificationMapper.toDomain(notificationMessage);
+
+ assertThat(notification.getTitle()).isEqualTo(notificationMessage.title);
+ assertThat(notification.getDescription()).isEqualTo(notificationMessage.description);
+ assertThat(notification.getAction().getType()).isEqualTo(notificationMessage.action.getType());
+ assertThat(notification.getAction().getTarget()).isEqualTo(notificationMessage.action.getTarget());
+ assertThat(notification.isRead()).isFalse();
+ assertThat(notification.getCreatedDate()).isNull();
+ assertThat(notification.getId()).isNull();
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/adapter/out/mongo/NotificationPersistenceAdapterShould.java b/src/test/java/com/productdock/library/notification/adapter/out/mongo/NotificationPersistenceAdapterShould.java
new file mode 100644
index 0000000..f92bf36
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/adapter/out/mongo/NotificationPersistenceAdapterShould.java
@@ -0,0 +1,53 @@
+package com.productdock.library.notification.adapter.out.mongo;
+
+import com.productdock.library.notification.adapter.out.mongo.mapper.NotificationEntityMapper;
+import com.productdock.library.notification.domain.Notification;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.ArrayList;
+
+import static com.productdock.library.notification.data.provider.domain.NotificationMother.notification;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class NotificationPersistenceAdapterShould {
+
+ private static final String USER_ID = "1";
+ private static final boolean READ = false;
+ private static final Notification NOTIFICATION = notification();
+
+ @InjectMocks
+ private NotificationPersistenceAdapter notificationPersistenceAdapter;
+ @Mock
+ private NotificationRepository notificationRepository;
+ @Mock
+ private NotificationEntityMapper notificationEntityMapper;
+
+ @Test
+ void getAllNotificationsForUser() {
+ notificationPersistenceAdapter.getAllByUserId(USER_ID);
+
+ verify(notificationRepository).findAllByUserId(USER_ID);
+ }
+
+ @Test
+ void getUnreadNotificationsForUser() {
+ notificationPersistenceAdapter.getUnreadByUserId(USER_ID);
+
+ verify(notificationRepository).findAllByReadAndUserId(READ, USER_ID);
+ }
+
+ @Test
+ void saveAll(){
+ var notifications = new ArrayList();
+ notifications.add(NOTIFICATION);
+ notificationPersistenceAdapter.saveAll(notifications);
+
+ verify(notificationRepository).saveAll(any());
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/adapter/out/mongo/mapper/NotificationMapperShould.java b/src/test/java/com/productdock/library/notification/adapter/out/mongo/mapper/NotificationMapperShould.java
new file mode 100644
index 0000000..5bee822
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/adapter/out/mongo/mapper/NotificationMapperShould.java
@@ -0,0 +1,49 @@
+package com.productdock.library.notification.adapter.out.mongo.mapper;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static com.productdock.library.notification.data.provider.domain.NotificationMother.notification;
+import static com.productdock.library.notification.data.provider.out.mongo.NotificationEntityMother.notificationEntity;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {NotificationEntityMapperImpl.class})
+class NotificationMapperShould {
+
+ @Autowired
+ private NotificationEntityMapper notificationEntityMapper;
+
+ @Test
+ void mapNotificationToNotificationEntity(){
+ var notification = notification();
+ var notificationEntity = notificationEntityMapper.toEntity(notification);
+
+ assertThat(notificationEntity.getId()).isEqualTo(notification.getId());
+ assertThat(notificationEntity.getUserId()).isEqualTo(notification.getUserId());
+ assertThat(notificationEntity.getTitle()).isEqualTo(notification.getTitle());
+ assertThat(notificationEntity.getDescription()).isEqualTo(notification.getDescription());
+ assertThat(notificationEntity.isRead()).isEqualTo(notification.isRead());
+ assertThat(notificationEntity.getCreatedDate()).isEqualTo(notification.getCreatedDate().toString());
+ assertThat(notificationEntity.getAction().getTarget()).isEqualTo(notification.getAction().getTarget());
+ assertThat(notificationEntity.getAction().getType()).isEqualTo(notification.getAction().getType());
+ }
+
+ @Test
+ void mapNotificationEntityToNotification(){
+ var notificationEntity = notificationEntity();
+ var notification = notificationEntityMapper.toDomain(notificationEntity);
+
+ assertThat(notification.getId()).isEqualTo(notificationEntity.getId());
+ assertThat(notification.getTitle()).isEqualTo(notificationEntity.getTitle());
+ assertThat(notification.getDescription()).isEqualTo(notificationEntity.getDescription());
+ assertThat(notification.getUserId()).isEqualTo(notificationEntity.getUserId());
+ assertThat(notification.getCreatedDate().toString()).isEqualTo(notificationEntity.getCreatedDate());
+ assertThat(notification.getAction().getTarget()).isEqualTo(notification.getAction().getTarget());
+ assertThat(notification.getAction().getType()).isEqualTo(notification.getAction().getType());
+ }
+
+}
diff --git a/src/test/java/com/productdock/library/notification/application/service/AddNotificationServiceShould.java b/src/test/java/com/productdock/library/notification/application/service/AddNotificationServiceShould.java
new file mode 100644
index 0000000..d051592
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/application/service/AddNotificationServiceShould.java
@@ -0,0 +1,30 @@
+package com.productdock.library.notification.application.service;
+
+import com.productdock.library.notification.application.port.out.persistence.NotificationPersistenceOutPort;
+import com.productdock.library.notification.domain.Notification;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static com.productdock.library.notification.data.provider.domain.NotificationMother.notification;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class AddNotificationServiceShould {
+
+ private static final Notification NOTIFICATION = notification();
+
+ @InjectMocks
+ private AddNotificationService addNotificationService;
+ @Mock
+ private NotificationPersistenceOutPort notificationPersistenceOutPort;
+
+ @Test
+ void addNotification(){
+ addNotificationService.saveNotification(NOTIFICATION);
+
+ verify(notificationPersistenceOutPort).save(NOTIFICATION);
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/application/service/GetNotificationsServiceShould.java b/src/test/java/com/productdock/library/notification/application/service/GetNotificationsServiceShould.java
new file mode 100644
index 0000000..657bc19
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/application/service/GetNotificationsServiceShould.java
@@ -0,0 +1,38 @@
+package com.productdock.library.notification.application.service;
+
+import com.productdock.library.notification.application.port.out.persistence.NotificationPersistenceOutPort;
+import com.productdock.library.notification.domain.Notification;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static com.productdock.library.notification.data.provider.domain.NotificationMother.notification;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class GetNotificationsServiceShould {
+
+ private static final String USER_ID = "1";
+ private static final Notification NOTIFICATION = notification();
+
+ @InjectMocks
+ private GetNotificationsService getNotificationsService;
+ @Mock
+ private NotificationPersistenceOutPort notificationPersistenceOutPort;
+
+ @Test
+ void getNotifications(){
+ getNotificationsService.getNotifications(USER_ID);
+
+ verify(notificationPersistenceOutPort).getAllByUserId(USER_ID);
+ }
+
+ @Test
+ void getUnreadNotificationsCount(){
+ getNotificationsService.getUnreadNotificationsCount(USER_ID);
+
+ verify(notificationPersistenceOutPort).getUnreadByUserId(USER_ID);
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/application/service/ReadNotificationServiceShould.java b/src/test/java/com/productdock/library/notification/application/service/ReadNotificationServiceShould.java
new file mode 100644
index 0000000..0cd23a0
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/application/service/ReadNotificationServiceShould.java
@@ -0,0 +1,33 @@
+package com.productdock.library.notification.application.service;
+
+import com.productdock.library.notification.application.port.out.persistence.NotificationPersistenceOutPort;
+import com.productdock.library.notification.domain.Notification;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.ArrayList;
+
+import static com.productdock.library.notification.data.provider.domain.NotificationMother.notification;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class ReadNotificationServiceShould {
+
+ private static final String USER_ID = "1";
+
+ @InjectMocks
+ private ReadNotificationsService readNotificationsService;
+ @Mock
+ private NotificationPersistenceOutPort notificationPersistenceOutPort;
+
+ @Test
+ void markNotificationAsRead(){
+ readNotificationsService.markAsReadNotifications(USER_ID);
+
+ verify(notificationPersistenceOutPort).saveAll(any());
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/config/SecurityConfigTest.java b/src/test/java/com/productdock/library/notification/config/SecurityConfigTest.java
new file mode 100644
index 0000000..2bbe855
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/config/SecurityConfigTest.java
@@ -0,0 +1,24 @@
+package com.productdock.library.notification.config;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class SecurityConfigTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ void givenUnauthenticated_thenUnauthorizedResponse() throws Exception {
+ mockMvc.perform(get("/api/notifications/"))
+ .andExpect(status().isUnauthorized());
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/data/provider/domain/NotificationMother.java b/src/test/java/com/productdock/library/notification/data/provider/domain/NotificationMother.java
new file mode 100644
index 0000000..e0e30d1
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/data/provider/domain/NotificationMother.java
@@ -0,0 +1,32 @@
+package com.productdock.library.notification.data.provider.domain;
+
+import com.productdock.library.notification.domain.Action;
+import com.productdock.library.notification.domain.Notification;
+
+import java.time.OffsetDateTime;
+
+public class NotificationMother {
+
+ public static final String defaultId = "11111";
+ public static final String defaultUserId = "userEmail";
+ public static final String defaultTitle = "title";
+ public static final String defaultDescription = "description";
+ public static final String defaultBookId = "1";
+ public static final String defaultType = "bookSubscription";
+ public static final Action defaultAction = Action.builder().target(defaultBookId).type(defaultType).build();
+
+ public static Notification notification(){
+ return notificationBuilder().build();
+ }
+
+ public static Notification.NotificationBuilder notificationBuilder(){
+ return Notification.builder()
+ .id(defaultId)
+ .userId(defaultUserId)
+ .title(defaultTitle)
+ .read(false)
+ .description(defaultDescription)
+ .createdDate(OffsetDateTime.now())
+ .action(defaultAction);
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/data/provider/in/kafka/NotificationMessageMother.java b/src/test/java/com/productdock/library/notification/data/provider/in/kafka/NotificationMessageMother.java
new file mode 100644
index 0000000..4553367
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/data/provider/in/kafka/NotificationMessageMother.java
@@ -0,0 +1,27 @@
+package com.productdock.library.notification.data.provider.in.kafka;
+
+import com.productdock.library.notification.adapter.in.kafka.messages.ActionMessage;
+import com.productdock.library.notification.adapter.in.kafka.messages.NotificationMessage;
+
+public class NotificationMessageMother {
+
+ private static final String defaultTitle = "title";
+ private static final String defaultDescription = "description";
+ private static final String defaultUserId = "userEmail";
+ private static final String defalutType = "bookSubscription";
+ private static final String defaultTarget = "1";
+ private static final ActionMessage defaultAction = ActionMessage.builder().type(defalutType).target(defaultTarget).build();
+
+
+ public static NotificationMessage notificationMessage() {
+ return notificationMessageBuilder().build();
+ }
+
+ public static NotificationMessage.NotificationMessageBuilder notificationMessageBuilder() {
+ return NotificationMessage.builder()
+ .title(defaultTitle)
+ .description(defaultDescription)
+ .userId(defaultUserId)
+ .action(defaultAction);
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/data/provider/out/mongo/NotificationEntityMother.java b/src/test/java/com/productdock/library/notification/data/provider/out/mongo/NotificationEntityMother.java
new file mode 100644
index 0000000..c1aa9f3
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/data/provider/out/mongo/NotificationEntityMother.java
@@ -0,0 +1,31 @@
+package com.productdock.library.notification.data.provider.out.mongo;
+
+import com.productdock.library.notification.adapter.out.mongo.enitity.ActionEntity;
+import com.productdock.library.notification.adapter.out.mongo.enitity.NotificationEntity;
+
+import java.time.OffsetDateTime;
+
+public class NotificationEntityMother {
+
+ public static final String defaultId = "11111";
+ public static final String defaultUserId = "userEmail";
+ public static final String defaultTitle = "title";
+ public static final String defaultDescription = "description";
+ public static final String defaultBookId = "1";
+ public static final String defaultType = "bookSubscription";
+ public static final ActionEntity defaultActionEntity = ActionEntity.builder().target(defaultBookId).type(defaultType).build();
+
+ public static NotificationEntity notificationEntity(){
+ return notificationBuilder().build();
+ }
+
+ public static NotificationEntity.NotificationEntityBuilder notificationBuilder(){
+ return NotificationEntity.builder()
+ .userId(defaultUserId)
+ .title(defaultTitle)
+ .read(false)
+ .description(defaultDescription)
+ .createdDate(OffsetDateTime.now().toString())
+ .action(defaultActionEntity);
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/integration/GetNotificationApiTest.java b/src/test/java/com/productdock/library/notification/integration/GetNotificationApiTest.java
new file mode 100644
index 0000000..c880944
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/integration/GetNotificationApiTest.java
@@ -0,0 +1,80 @@
+package com.productdock.library.notification.integration;
+
+import com.productdock.library.notification.adapter.out.mongo.NotificationRepository;
+import com.productdock.library.notification.adapter.out.mongo.enitity.NotificationEntity;
+import com.productdock.library.notification.integration.kafka.KafkaTestBase;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static com.productdock.library.notification.data.provider.out.mongo.NotificationEntityMother.notificationEntity;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class GetNotificationApiTest extends KafkaTestBase {
+
+ public static final String USER_ID = "userEmail";
+
+ public static final NotificationEntity NOTIFICATION_ENTITY = notificationEntity();
+
+ @Autowired
+ private MockMvc mockMvc;
+ @Autowired
+ private NotificationRepository notificationRepository;
+
+ @BeforeEach
+ @AfterEach
+ void before() {
+ notificationRepository.deleteAll();
+ }
+
+ @Test
+ @WithMockUser
+ void shouldReturnNotifications() throws Exception {
+ notificationRepository.save(NOTIFICATION_ENTITY);
+
+ mockMvc.perform(get("/api/notifications")
+ .with(jwt().jwt(jwt -> {
+ jwt.claim("email", USER_ID);
+ })))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ @WithMockUser
+ void shouldReturnUnreadNotificationsCount() throws Exception {
+ notificationRepository.save(NOTIFICATION_ENTITY);
+
+ var response = mockMvc.perform(get("/api/notifications/unread")
+ .with(jwt().jwt(jwt -> {
+ jwt.claim("email", USER_ID);
+ })))
+ .andExpect(status().isOk())
+ .andReturn().getResponse();
+
+ assertThat(response.getContentAsString()).isEqualTo("1");
+ }
+
+ @Test
+ @WithMockUser
+ void shouldMarkNotificationsAsRead() throws Exception {
+ notificationRepository.save(NOTIFICATION_ENTITY);
+
+ mockMvc.perform(post("/api/notifications/read")
+ .with(jwt().jwt(jwt -> {
+ jwt.claim("email", USER_ID);
+ })))
+ .andExpect(status().isOk());
+
+ var notifications = notificationRepository.findAllByUserId(USER_ID);
+ notifications.forEach(notificationEntity -> assertThat(notificationEntity.isRead()).isTrue());
+ }
+
+}
diff --git a/src/test/java/com/productdock/library/notification/integration/ReceiveNotificationTest.java b/src/test/java/com/productdock/library/notification/integration/ReceiveNotificationTest.java
new file mode 100644
index 0000000..ddcd417
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/integration/ReceiveNotificationTest.java
@@ -0,0 +1,62 @@
+package com.productdock.library.notification.integration;
+
+import com.productdock.library.notification.adapter.in.kafka.messages.NotificationMessage;
+import com.productdock.library.notification.adapter.out.mongo.NotificationRepository;
+import com.productdock.library.notification.adapter.out.mongo.enitity.NotificationEntity;
+import com.productdock.library.notification.integration.kafka.KafkaTestBase;
+import com.productdock.library.notification.integration.kafka.KafkaTestProducer;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.mongodb.core.MongoTemplate;
+
+import java.time.Duration;
+
+import static com.productdock.library.notification.data.provider.in.kafka.NotificationMessageMother.notificationMessage;
+import static com.productdock.library.notification.data.provider.out.mongo.NotificationEntityMother.notificationEntity;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@Slf4j
+public class ReceiveNotificationTest extends KafkaTestBase {
+
+ public static final NotificationMessage NOTIFICATION_MESSAGE = notificationMessage();
+ @Autowired
+ private KafkaTestProducer producer;
+ @Autowired
+ private NotificationRepository notificationRepository;
+ @Value("${spring.kafka.topic.notifications}")
+ private String topic;
+
+ @Autowired
+ private MongoTemplate mongoTemplate;
+
+ @BeforeEach
+ void before() {
+ //mongoTemplate.remove(new Query(), "notifications");
+ notificationRepository.deleteAll();
+ }
+
+ @Test
+ void shouldSaveNotification_whenMessageReceived() throws Exception {
+ producer.sendNotificationMessage(topic, NOTIFICATION_MESSAGE);
+
+ notificationRepository.save(notificationEntity());
+ await()
+ .atMost(Duration.ofSeconds(20))
+ .until(() -> {
+ log.info("Fetching...");
+ var savedNotification = notificationRepository.findAll();
+ log.info("Fetched notifications:{}", savedNotification);
+ return !savedNotification.isEmpty();
+ });
+
+ log.warn("{}", notificationRepository.findAllByUserId(NOTIFICATION_MESSAGE.userId));
+ var notifications = notificationRepository.findAllByUserId(NOTIFICATION_MESSAGE.userId);
+ assertThat(notifications).isNotEmpty();
+ }
+}
diff --git a/src/test/java/com/productdock/library/notification/integration/kafka/KafkaTestBase.java b/src/test/java/com/productdock/library/notification/integration/kafka/KafkaTestBase.java
new file mode 100644
index 0000000..ee62df8
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/integration/kafka/KafkaTestBase.java
@@ -0,0 +1,10 @@
+package com.productdock.library.notification.integration.kafka;
+
+
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.kafka.test.context.EmbeddedKafka;
+
+@AutoConfigureMockMvc
+@EmbeddedKafka(partitions = 1, brokerProperties = {"listeners=PLAINTEXT://localhost:9093", "port=9093"})
+public class KafkaTestBase {
+}
diff --git a/src/test/java/com/productdock/library/notification/integration/kafka/KafkaTestProducer.java b/src/test/java/com/productdock/library/notification/integration/kafka/KafkaTestProducer.java
new file mode 100644
index 0000000..d11fe77
--- /dev/null
+++ b/src/test/java/com/productdock/library/notification/integration/kafka/KafkaTestProducer.java
@@ -0,0 +1,22 @@
+package com.productdock.library.notification.integration.kafka;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.productdock.library.notification.adapter.in.kafka.messages.NotificationMessage;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class KafkaTestProducer {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ @Autowired
+ private KafkaTemplate kafkaTemplate;
+
+ public void sendNotificationMessage(String topic, NotificationMessage notificationMessage) throws JsonProcessingException {
+ String message = OBJECT_MAPPER.writeValueAsString(notificationMessage);
+ kafkaTemplate.send(topic, message);
+ }
+}
diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml
new file mode 100644
index 0000000..6a35cda
--- /dev/null
+++ b/src/test/resources/application.yaml
@@ -0,0 +1,23 @@
+spring:
+ mongodb:
+ embedded:
+ version: 3.5.5
+ kafka:
+ bootstrap-servers: localhost:9093
+ producer:
+ client-id: kafka-message-producer
+ acks: all
+ value-seriliazer: org.apache.kafka.common.serialization.StringSerializer
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ consumer:
+ auto-offset-reset: earliest
+ group-id: rbc-library
+ topic:
+ notifications: test-notifications
+
+logging:
+ level:
+ com.productdock.library.rental: DEBUG
+
+jwt:
+ public.key: classpath:test.pub
\ No newline at end of file
diff --git a/src/test/resources/test.pub b/src/test/resources/test.pub
new file mode 100644
index 0000000..0b2ee7b
--- /dev/null
+++ b/src/test/resources/test.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd
+7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv
+c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6
+iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2
+kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o
+RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj
+KwIDAQAB
+-----END PUBLIC KEY-----
\ No newline at end of file