diff --git a/pom.xml b/pom.xml
index 405168e..ba7f1d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,6 +96,11 @@
spring-boot-starter-webmvc-test
test
+
+ cn.hutool
+ hutool-core
+ 5.8.43
+
diff --git a/src/main/java/dev/mednikov/social/connections/config/JdbcTemplateConfig.java b/src/main/java/dev/mednikov/social/connections/config/JdbcTemplateConfig.java
new file mode 100644
index 0000000..5ae72d0
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/config/JdbcTemplateConfig.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/dev/mednikov/social/connections/config/ObjectMapperConfig.java b/src/main/java/dev/mednikov/social/connections/config/ObjectMapperConfig.java
index 37d5b58..aaa376e 100644
--- a/src/main/java/dev/mednikov/social/connections/config/ObjectMapperConfig.java
+++ b/src/main/java/dev/mednikov/social/connections/config/ObjectMapperConfig.java
@@ -1,5 +1,7 @@
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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -10,6 +12,7 @@ public class ObjectMapperConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return objectMapper;
}
}
diff --git a/src/main/java/dev/mednikov/social/connections/messaging/UserCreatedListener.java b/src/main/java/dev/mednikov/social/connections/messaging/UserCreatedListener.java
new file mode 100644
index 0000000..176819d
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/messaging/UserCreatedListener.java
@@ -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());
+ }
+ }
+}
diff --git a/src/main/java/dev/mednikov/social/connections/messaging/UserParamsMapper.java b/src/main/java/dev/mednikov/social/connections/messaging/UserParamsMapper.java
new file mode 100644
index 0000000..45e7188
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/messaging/UserParamsMapper.java
@@ -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 {
+
+ @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;
+ }
+}
diff --git a/src/main/java/dev/mednikov/social/connections/messaging/UserPayload.java b/src/main/java/dev/mednikov/social/connections/messaging/UserPayload.java
new file mode 100644
index 0000000..0fb5780
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/messaging/UserPayload.java
@@ -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;
+ }
+}
diff --git a/src/main/java/dev/mednikov/social/connections/messaging/UserUpdatedListener.java b/src/main/java/dev/mednikov/social/connections/messaging/UserUpdatedListener.java
new file mode 100644
index 0000000..22dcf9d
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/messaging/UserUpdatedListener.java
@@ -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());
+ }
+ }
+}
diff --git a/src/main/java/dev/mednikov/social/connections/models/User.java b/src/main/java/dev/mednikov/social/connections/models/User.java
new file mode 100644
index 0000000..46ffe12
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/models/User.java
@@ -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;
+ }
+
+
+}
diff --git a/src/main/java/dev/mednikov/social/connections/repositories/IdentifierGenerator.java b/src/main/java/dev/mednikov/social/connections/repositories/IdentifierGenerator.java
new file mode 100644
index 0000000..11bb3d7
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/repositories/IdentifierGenerator.java
@@ -0,0 +1,7 @@
+package dev.mednikov.social.connections.repositories;
+
+public interface IdentifierGenerator {
+
+ Long getNextId();
+
+}
diff --git a/src/main/java/dev/mednikov/social/connections/repositories/IdentifierGeneratorImpl.java b/src/main/java/dev/mednikov/social/connections/repositories/IdentifierGeneratorImpl.java
new file mode 100644
index 0000000..bb96e26
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/repositories/IdentifierGeneratorImpl.java
@@ -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();
+ }
+}
diff --git a/src/main/java/dev/mednikov/social/connections/repositories/UserRepository.java b/src/main/java/dev/mednikov/social/connections/repositories/UserRepository.java
new file mode 100644
index 0000000..56dd426
--- /dev/null
+++ b/src/main/java/dev/mednikov/social/connections/repositories/UserRepository.java
@@ -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 {
+
+}
diff --git a/src/main/resources/db/migration/V1__initial.sql b/src/main/resources/db/migration/V1__initial.sql
new file mode 100644
index 0000000..be121e7
--- /dev/null
+++ b/src/main/resources/db/migration/V1__initial.sql
@@ -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
+);
\ No newline at end of file