Implemented user event listeners

This commit is contained in:
2026-01-10 16:22:51 +01:00
parent 17ef2e4c94
commit 2899671321
12 changed files with 437 additions and 0 deletions

View File

@@ -96,6 +96,11 @@
<artifactId>spring-boot-starter-webmvc-test</artifactId> <artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.43</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -0,0 +1,17 @@
package dev.mednikov.social.connections.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class JdbcTemplateConfig {
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
}

View File

@@ -1,5 +1,7 @@
package dev.mednikov.social.connections.config; package dev.mednikov.social.connections.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -10,6 +12,7 @@ public class ObjectMapperConfig {
@Bean @Bean
public ObjectMapper objectMapper() { public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return objectMapper; return objectMapper;
} }
} }

View File

@@ -0,0 +1,63 @@
package dev.mednikov.social.connections.messaging;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.mednikov.social.connections.models.User;
import jakarta.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;
@Component
public class UserCreatedListener {
private final static UserParamsMapper userParamsMapper = new UserParamsMapper();
private final static Logger logger = LoggerFactory.getLogger(UserCreatedListener.class);
private final NamedParameterJdbcTemplate jdbcTemplate;
private final ObjectMapper objectMapper;
public UserCreatedListener(NamedParameterJdbcTemplate jdbcTemplate, ObjectMapper objectMapper) {
this.jdbcTemplate = jdbcTemplate;
this.objectMapper = objectMapper;
}
@Transactional
@RabbitListener(queues = {"social_users_created"})
public void onEvent (String payload) {
logger.info("User created" + payload);
try {
UserPayload user = this.objectMapper.readValue(payload, UserPayload.class);
String query = """
INSERT INTO connections_user (
id,
first_name,
last_name,
email,
is_active,
is_onboarded,
is_email_verified,
avatar_url,
headline,
version
) VALUES (
:id,
:first_name,
:last_name,
:email,
:is_active,
:is_onboarded,
:is_email_verified,
:avatar_url,
:headline,
:version) ON CONFLICT (id) DO NOTHING
""";
MapSqlParameterSource sqlParameterSource = userParamsMapper.apply(user);
jdbcTemplate.update(query, sqlParameterSource);
} catch (Exception e) {
logger.error(e.getMessage());
}
}
}

View File

@@ -0,0 +1,24 @@
package dev.mednikov.social.connections.messaging;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import java.util.function.Function;
final class UserParamsMapper implements Function<UserPayload, MapSqlParameterSource> {
@Override
public MapSqlParameterSource apply(UserPayload user) {
MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource();
sqlParameterSource.addValue("id", Long.valueOf(user.getId()));
sqlParameterSource.addValue("first_name", user.getFirstName());
sqlParameterSource.addValue("last_name", user.getLastName());
sqlParameterSource.addValue("email", user.getEmail());
sqlParameterSource.addValue("is_active", user.isActive());
sqlParameterSource.addValue("is_onboarded", user.isOnboarded());
sqlParameterSource.addValue("is_email_verified", user.isEmailVerified());
sqlParameterSource.addValue("avatar_url", user.getAvatarUrl());
sqlParameterSource.addValue("headline", user.getHeadline());
sqlParameterSource.addValue("version", user.getVersion());
return sqlParameterSource;
}
}

View File

@@ -0,0 +1,112 @@
package dev.mednikov.social.connections.messaging;
import dev.mednikov.social.connections.models.User;
final class UserPayload {
private String id;
private String firstName;
private String lastName;
private String email;
private String avatarUrl;
private boolean emailVerified;
private boolean active;
private boolean onboarded;
private String headline;
private Long version;
UserPayload() {}
UserPayload(User user) {
this.id = user.getId().toString();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.email = user.getEmail();
this.avatarUrl = user.getAvatarUrl();
this.emailVerified = user.getEmailVerified();
this.active = user.getActive();
this.onboarded = user.getOnboarded();
this.headline = user.getHeadline();
this.version = user.getVersion();
}
String getId() {
return id;
}
String getFirstName() {
return firstName;
}
String getLastName() {
return lastName;
}
String getEmail() {
return email;
}
String getAvatarUrl() {
return avatarUrl;
}
boolean isEmailVerified() {
return emailVerified;
}
boolean isActive() {
return active;
}
boolean isOnboarded() {
return onboarded;
}
String getHeadline() {
return headline;
}
Long getVersion() {
return version;
}
public void setId(String id) {
this.id = id;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setEmail(String email) {
this.email = email;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public void setEmailVerified(boolean emailVerified) {
this.emailVerified = emailVerified;
}
public void setActive(boolean active) {
this.active = active;
}
public void setOnboarded(boolean onboarded) {
this.onboarded = onboarded;
}
public void setHeadline(String headline) {
this.headline = headline;
}
public void setVersion(Long version) {
this.version = version;
}
}

View File

@@ -0,0 +1,52 @@
package dev.mednikov.social.connections.messaging;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.mednikov.social.connections.models.User;
import jakarta.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;
@Component
public class UserUpdatedListener {
private final static UserParamsMapper userParamsMapper = new UserParamsMapper();
private final static Logger logger = LoggerFactory.getLogger(UserUpdatedListener.class);
private final NamedParameterJdbcTemplate jdbcTemplate;
private final ObjectMapper objectMapper;
public UserUpdatedListener(NamedParameterJdbcTemplate jdbcTemplate, ObjectMapper objectMapper) {
this.jdbcTemplate = jdbcTemplate;
this.objectMapper = objectMapper;
}
@Transactional
@RabbitListener(queues = {"social_users_updated"})
public void onEvent (String payload){
logger.info("User updated" + payload);
try {
UserPayload user = this.objectMapper.readValue(payload, UserPayload.class);
String query = """
UPDATE connections_user
SET first_name = :first_name,
last_name = :last_name,
email = :email,
is_active = :is_active,
is_email_verified = :is_email_verified,
is_onboarded = :is_onboarded,
avatar_url = :avatar_url,
headline = :headline,
version = :version
WHERE id = :id
""";
MapSqlParameterSource sqlParameterSource = userParamsMapper.apply(user);
jdbcTemplate.update(query, sqlParameterSource);
} catch (Exception ex){
logger.error(ex.getMessage());
}
}
}

View File

@@ -0,0 +1,117 @@
package dev.mednikov.social.connections.models;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "connections_user")
public class User {
@Id private Long id;
@Column(nullable = false, name = "email") private String email;
@Column(nullable = false, name = "first_name") private String firstName;
@Column(nullable = false, name = "last_name") private String lastName;
@Column(nullable = false, name = "avatar_url") private String avatarUrl;
@Column(nullable = false, name = "headline") private String headline;
@Column(nullable = false, name = "is_active") private Boolean active;
@Column(nullable = false, name = "is_email_verified") private Boolean emailVerified;
@Column(nullable = false, name = "is_onboarded") private Boolean onboarded;
@Column(nullable = false, name = "version") private Long version;
@Override
public final boolean equals(Object o) {
if (!(o instanceof User user)) return false;
return id.equals(user.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Boolean getOnboarded() {
return onboarded;
}
public void setOnboarded(Boolean onboarded) {
this.onboarded = onboarded;
}
public Boolean getEmailVerified() {
return emailVerified;
}
public void setEmailVerified(Boolean emailVerified) {
this.emailVerified = emailVerified;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getHeadline() {
return headline;
}
public void setHeadline(String headline) {
this.headline = headline;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@@ -0,0 +1,7 @@
package dev.mednikov.social.connections.repositories;
public interface IdentifierGenerator {
Long getNextId();
}

View File

@@ -0,0 +1,17 @@
package dev.mednikov.social.connections.repositories;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
public class IdentifierGeneratorImpl implements IdentifierGenerator {
private final SnowflakeGenerator snowflakeGenerator;
public IdentifierGeneratorImpl() {
this.snowflakeGenerator = new SnowflakeGenerator();
}
@Override
public Long getNextId() {
return this.snowflakeGenerator.next();
}
}

View File

@@ -0,0 +1,8 @@
package dev.mednikov.social.connections.repositories;
import dev.mednikov.social.connections.models.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}

View File

@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS connections_user (
id BIGINT PRIMARY KEY,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
avatar_url VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL,
is_onboarded BOOLEAN NOT NULL,
is_email_verified BOOLEAN NOT NULL,
headline VARCHAR(255) NOT NULL,
version BIGINT NOT NULL
);