From 964547dcc9064f9f208f7e80eb0fcf73e3a810d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C4=8Cuturi=C4=87?= Date: Mon, 16 Feb 2026 16:17:32 +0100 Subject: [PATCH] feat: Add preference init on user register --- build.gradle.kts | 6 +++ environment/.local.env | 7 +++- .../devoops/user/config/RabbitMQConfig.java | 38 +++++++++++++++++++ .../user/dto/message/UserCreatedMessage.java | 20 ++++++++++ .../user/service/AuthenticationService.java | 3 ++ .../service/UserEventPublisherService.java | 35 +++++++++++++++++ src/main/resources/application.properties | 11 ++++++ .../AuthenticationIntegrationTest.java | 9 +++++ .../service/AuthenticationServiceTest.java | 3 ++ 9 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/devoops/user/config/RabbitMQConfig.java create mode 100644 src/main/java/com/devoops/user/dto/message/UserCreatedMessage.java create mode 100644 src/main/java/com/devoops/user/service/UserEventPublisherService.java diff --git a/build.gradle.kts b/build.gradle.kts index 8bac8b5..39bcd1a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,6 +37,10 @@ dependencies { // Security implementation("org.springframework.boot:spring-boot-starter-security") + // RabbitMQ + implementation("org.springframework.boot:spring-boot-starter-amqp") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") + // JWT implementation("io.jsonwebtoken:jjwt-api:0.12.6") runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.6") @@ -65,6 +69,8 @@ dependencies { testImplementation("org.springframework.security:spring-security-test") testImplementation("org.testcontainers:junit-jupiter:1.20.4") testImplementation("org.testcontainers:postgresql:1.20.4") + testImplementation("org.springframework.amqp:spring-rabbit-test") + testImplementation("org.testcontainers:rabbitmq:1.20.4") testImplementation("io.rest-assured:rest-assured:5.5.0") testCompileOnly("org.projectlombok:lombok") diff --git a/environment/.local.env b/environment/.local.env index 5dfaddc..b956e2f 100644 --- a/environment/.local.env +++ b/environment/.local.env @@ -5,4 +5,9 @@ ZIPKIN_PORT=9411 POSTGRES_HOST=devoops-postgres POSGTES_PORT=5432 DB_USERNAME=user-service -DB_PASSWORD=user-service-pass \ No newline at end of file +DB_PASSWORD=user-service-pass +RABBITMQ_HOST=devoops-rabbitmq +RABBITMQ_PORT=5672 +RABBITMQ_USERNAME=devoops +RABBITMQ_PASSWORD=devoops123 +RABBITMQ_VHOST=/ \ No newline at end of file diff --git a/src/main/java/com/devoops/user/config/RabbitMQConfig.java b/src/main/java/com/devoops/user/config/RabbitMQConfig.java new file mode 100644 index 0000000..0884697 --- /dev/null +++ b/src/main/java/com/devoops/user/config/RabbitMQConfig.java @@ -0,0 +1,38 @@ +package com.devoops.user.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitMQConfig { + + @Value("${rabbitmq.exchange.notification}") + private String notificationExchange; + + @Bean + public MessageConverter jsonMessageConverter() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + return new Jackson2JsonMessageConverter(objectMapper); + } + + @Bean + public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMessageConverter(jsonMessageConverter()); + return rabbitTemplate; + } + + @Bean + public TopicExchange notificationExchange() { + return new TopicExchange(notificationExchange); + } +} diff --git a/src/main/java/com/devoops/user/dto/message/UserCreatedMessage.java b/src/main/java/com/devoops/user/dto/message/UserCreatedMessage.java new file mode 100644 index 0000000..55b14c8 --- /dev/null +++ b/src/main/java/com/devoops/user/dto/message/UserCreatedMessage.java @@ -0,0 +1,20 @@ +package com.devoops.user.dto.message; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.UUID; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UserCreatedMessage implements Serializable { + private UUID userId; + private String userEmail; +} diff --git a/src/main/java/com/devoops/user/service/AuthenticationService.java b/src/main/java/com/devoops/user/service/AuthenticationService.java index da2a7ad..7d94d19 100644 --- a/src/main/java/com/devoops/user/service/AuthenticationService.java +++ b/src/main/java/com/devoops/user/service/AuthenticationService.java @@ -28,6 +28,7 @@ public class AuthenticationService { private final JwtService jwtService; private final AuthenticationManager authenticationManager; private final UserMapper userMapper; + private final UserEventPublisherService userEventPublisherService; @Transactional public AuthenticationResponse register(RegisterRequest request) { @@ -52,6 +53,8 @@ public AuthenticationResponse register(RegisterRequest request) { log.info("Registration successful for user: {} (id: {}, role: {})", savedUser.getUsername(), savedUser.getId(), savedUser.getRole()); + userEventPublisherService.publishUserCreated(savedUser.getId(), savedUser.getEmail()); + return new AuthenticationResponse(token, jwtService.getExpirationTime(), userMapper.toUserResponse(savedUser)); } diff --git a/src/main/java/com/devoops/user/service/UserEventPublisherService.java b/src/main/java/com/devoops/user/service/UserEventPublisherService.java new file mode 100644 index 0000000..4f1bef8 --- /dev/null +++ b/src/main/java/com/devoops/user/service/UserEventPublisherService.java @@ -0,0 +1,35 @@ +package com.devoops.user.service; + +import com.devoops.user.dto.message.UserCreatedMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserEventPublisherService { + + private final RabbitTemplate rabbitTemplate; + + @Value("${rabbitmq.exchange.notification}") + private String notificationExchange; + + @Value("${rabbitmq.routing-key.user-created}") + private String userCreatedRoutingKey; + + public void publishUserCreated(UUID userId, String email) { + UserCreatedMessage message = UserCreatedMessage.builder() + .userId(userId) + .userEmail(email) + .build(); + + log.info("Publishing user.created event for userId: {}, email: {}", userId, email); + rabbitTemplate.convertAndSend(notificationExchange, userCreatedRoutingKey, message); + log.debug("Successfully published user.created event for userId: {}", userId); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 468b5fb..de860a4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,3 +34,14 @@ cors.allowed-origins=${CORS_ALLOWED_ORIGINS:http://localhost:4200} management.endpoints.web.exposure.include=health,prometheus,metrics management.endpoint.health.show-details=always management.prometheus.metrics.export.enabled=true + +# RabbitMQ +spring.rabbitmq.host=${RABBITMQ_HOST:localhost} +spring.rabbitmq.port=${RABBITMQ_PORT:5672} +spring.rabbitmq.username=${RABBITMQ_USERNAME:devoops} +spring.rabbitmq.password=${RABBITMQ_PASSWORD:devoops123} +spring.rabbitmq.virtual-host=${RABBITMQ_VHOST:/} + +# Queue config +rabbitmq.exchange.notification=notification.exchange +rabbitmq.routing-key.user-created=user.created diff --git a/src/test/java/com/devoops/user/integration/AuthenticationIntegrationTest.java b/src/test/java/com/devoops/user/integration/AuthenticationIntegrationTest.java index 9ef149a..d3ecf17 100644 --- a/src/test/java/com/devoops/user/integration/AuthenticationIntegrationTest.java +++ b/src/test/java/com/devoops/user/integration/AuthenticationIntegrationTest.java @@ -14,6 +14,7 @@ import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.containers.RabbitMQContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -32,6 +33,10 @@ class AuthenticationIntegrationTest { .withUsername("test") .withPassword("test"); + @Container + static RabbitMQContainer rabbitmq = new RabbitMQContainer("rabbitmq:3-management-alpine") + .withExposedPorts(5672, 15672); + @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); @@ -39,6 +44,10 @@ static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.password", postgres::getPassword); registry.add("spring.flyway.enabled", () -> "true"); registry.add("spring.jpa.hibernate.ddl-auto", () -> "validate"); + registry.add("spring.rabbitmq.host", rabbitmq::getHost); + registry.add("spring.rabbitmq.port", rabbitmq::getAmqpPort); + registry.add("spring.rabbitmq.username", () -> "guest"); + registry.add("spring.rabbitmq.password", () -> "guest"); } @LocalServerPort diff --git a/src/test/java/com/devoops/user/service/AuthenticationServiceTest.java b/src/test/java/com/devoops/user/service/AuthenticationServiceTest.java index fcdc6eb..15d1970 100644 --- a/src/test/java/com/devoops/user/service/AuthenticationServiceTest.java +++ b/src/test/java/com/devoops/user/service/AuthenticationServiceTest.java @@ -51,6 +51,9 @@ class AuthenticationServiceTest { @Mock private UserMapper userMapper; + @Mock + private UserEventPublisherService userEventPublisherService; + @InjectMocks private AuthenticationService authenticationService;