diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index d40619c..21b1c22 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -2,15 +2,27 @@ version: '3.8'
services:
db:
- image: postgres:latest
+ image: postgres:16-alpine
environment:
- POSTGRES_DB: your_database
- POSTGRES_USER: your_user
- POSTGRES_PASSWORD: your_password
+ POSTGRES_DB: rosetta_db
+ POSTGRES_USER: rosetta_user
+ POSTGRES_PASSWORD: rosetta_password
ports:
- "5432:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U rosetta_user"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
adminer:
image: adminer
ports:
- - "8080:8080"
\ No newline at end of file
+ - "8080:8080"
+ depends_on:
+ - db
+
+volumes:
+ postgres_data:
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 19b9e3e..c84b6a0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,13 +16,31 @@
-
-
org.java-websocket
Java-WebSocket
1.6.0
+
+
+ org.hibernate.orm
+ hibernate-core
+ 6.4.0.Final
+
+
+
+
+ org.postgresql
+ postgresql
+ 42.7.1
+
+
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+ 3.1.0
+
\ No newline at end of file
diff --git a/src/main/java/com/rosetta/im/database/BaseEntity.java b/src/main/java/com/rosetta/im/database/BaseEntity.java
new file mode 100644
index 0000000..bb11391
--- /dev/null
+++ b/src/main/java/com/rosetta/im/database/BaseEntity.java
@@ -0,0 +1,29 @@
+package com.rosetta.im.database;
+
+public class BaseEntity {
+
+ /**
+ * Сохраняет сущность в базе данных.
+ * @param entity Сущность для сохранения.
+ */
+ public void save(Object entity) {
+ DatabaseManager.save(entity);
+ }
+
+ /**
+ * Обновляет сущность в базе данных.
+ * @param entity Сущность для обновления.
+ */
+ public void update(Object entity) {
+ DatabaseManager.update(entity);
+ }
+
+ /**
+ * Удаляет сущность из базы данных.
+ * @param entity Сущность для удаления.
+ */
+ public void delete(Object entity) {
+ DatabaseManager.delete(entity);
+ }
+
+}
diff --git a/src/main/java/com/rosetta/im/database/DatabaseManager.java b/src/main/java/com/rosetta/im/database/DatabaseManager.java
new file mode 100644
index 0000000..62029b7
--- /dev/null
+++ b/src/main/java/com/rosetta/im/database/DatabaseManager.java
@@ -0,0 +1,80 @@
+package com.rosetta.im.database;
+
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+
+import java.util.List;
+
+public class DatabaseManager {
+
+ public static T save(T entity) {
+ Session session = HibernateUtil.openSession();
+ Transaction transaction = null;
+ try {
+ transaction = session.beginTransaction();
+ session.persist(entity);
+ transaction.commit();
+ return entity;
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw new RuntimeException("Error saving entity: " + e.getMessage(), e);
+ } finally {
+ session.close();
+ }
+ }
+
+ public static T update(T entity) {
+ Session session = HibernateUtil.openSession();
+ Transaction transaction = null;
+ try {
+ transaction = session.beginTransaction();
+ session.merge(entity);
+ transaction.commit();
+ return entity;
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw new RuntimeException("Error updating entity: " + e.getMessage(), e);
+ } finally {
+ session.close();
+ }
+ }
+
+ public static T findById(Class entityClass, Long id) {
+ Session session = HibernateUtil.openSession();
+ try {
+ return session.find(entityClass, id);
+ } finally {
+ session.close();
+ }
+ }
+
+ public static List findAll(Class entityClass) {
+ Session session = HibernateUtil.openSession();
+ try {
+ return session.createQuery("FROM " + entityClass.getSimpleName(), entityClass).list();
+ } finally {
+ session.close();
+ }
+ }
+
+ public static void delete(T entity) {
+ Session session = HibernateUtil.openSession();
+ Transaction transaction = null;
+ try {
+ transaction = session.beginTransaction();
+ session.remove(entity);
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw new RuntimeException("Error deleting entity: " + e.getMessage(), e);
+ } finally {
+ session.close();
+ }
+ }
+}
diff --git a/src/main/java/com/rosetta/im/database/HibernateUtil.java b/src/main/java/com/rosetta/im/database/HibernateUtil.java
new file mode 100644
index 0000000..a1596de
--- /dev/null
+++ b/src/main/java/com/rosetta/im/database/HibernateUtil.java
@@ -0,0 +1,33 @@
+package com.rosetta.im.database;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.cfg.Configuration;
+
+public class HibernateUtil {
+ private static final SessionFactory sessionFactory;
+
+ static {
+ try {
+ sessionFactory = new Configuration().configure().buildSessionFactory();
+ } catch (Exception e) {
+ throw new ExceptionInInitializerError("Error initializing Hibernate: " + e.getMessage());
+ }
+ }
+
+ public static SessionFactory getSessionFactory() {
+ return sessionFactory;
+ }
+
+ public static Session getCurrentSession() {
+ return sessionFactory.getCurrentSession();
+ }
+
+ public static Session openSession() {
+ return sessionFactory.openSession();
+ }
+
+ public static void shutdown() {
+ sessionFactory.close();
+ }
+}
diff --git a/src/main/java/com/rosetta/im/database/converters/StringListConverter.java b/src/main/java/com/rosetta/im/database/converters/StringListConverter.java
new file mode 100644
index 0000000..6bd4d5b
--- /dev/null
+++ b/src/main/java/com/rosetta/im/database/converters/StringListConverter.java
@@ -0,0 +1,27 @@
+package com.rosetta.im.database.converters;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+@Converter
+public class StringListConverter implements AttributeConverter, String> {
+ @Override
+ public String convertToDatabaseColumn(List attribute) {
+ if (attribute == null || attribute.isEmpty()) {
+ return "";
+ }
+ return String.join(",", attribute);
+ }
+
+ @Override
+ public List convertToEntityAttribute(String dbData) {
+ if (dbData == null || dbData.isBlank()) {
+ return new ArrayList<>();
+ }
+ return new ArrayList<>(Arrays.asList(dbData.split(",")));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/rosetta/im/database/entity/User.java b/src/main/java/com/rosetta/im/database/entity/User.java
new file mode 100644
index 0000000..e6d7a40
--- /dev/null
+++ b/src/main/java/com/rosetta/im/database/entity/User.java
@@ -0,0 +1,229 @@
+package com.rosetta.im.database.entity;
+
+import com.rosetta.im.database.BaseEntity;
+import com.rosetta.im.database.HibernateUtil;
+import com.rosetta.im.database.converters.StringListConverter;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Convert;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Index;
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.PreUpdate;
+import jakarta.persistence.Table;
+import org.hibernate.Session;
+import org.hibernate.Transaction;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Entity
+@Table(name = "users", indexes = {
+ @Index(name = "idx_users_publickey", columnList = "publicKey", unique = true)
+})
+public class User extends BaseEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "privateKey", nullable = false)
+ private String privateKey;
+
+ @Column(name = "username")
+ private String username;
+
+ @Column(name = "title")
+ private String title;
+
+ @Column(name = "publicKey", nullable = false, unique = true)
+ private String publicKey;
+
+ @Column(name = "timestamp", nullable = false)
+ private long timestamp;
+
+ @Column(name = "createdTime", nullable = false, updatable = false)
+ private Date createdTime;
+
+ @Column(name = "verified", nullable = false)
+ private int verified;
+
+ @Convert(converter = StringListConverter.class)
+ @Column(name = "notificationsTokens", nullable = false)
+ private List notificationsTokens = new ArrayList<>();
+
+ @PrePersist
+ protected void onCreate() {
+ createdTime = new Date();
+ }
+
+ @PreUpdate
+ protected void onUpdate() {
+ if (createdTime == null) {
+ createdTime = new Date();
+ }
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getPrivateKey() {
+ return privateKey;
+ }
+
+ public void setPrivateKey(String privateKey) {
+ this.privateKey = privateKey;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(String publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public Date getCreatedTime() {
+ return createdTime;
+ }
+
+ public int getVerified() {
+ return verified;
+ }
+
+ public void setVerified(int verified) {
+ this.verified = verified;
+ }
+
+ public List getNotificationsTokens() {
+ return notificationsTokens;
+ }
+
+ public void setNotificationsTokens(List notificationsTokens) {
+ this.notificationsTokens = notificationsTokens;
+ }
+
+ public static User getByPrivateKey(String privateKey) {
+ try (Session session = HibernateUtil.openSession()) {
+ return session.createQuery(
+ "from User where privateKey = :privateKey", User.class)
+ .setParameter("privateKey", privateKey)
+ .uniqueResult();
+ }
+ }
+
+ public static User getByUsername(String username) {
+ try (Session session = HibernateUtil.openSession()) {
+ return session.createQuery(
+ "from User where username = :username", User.class)
+ .setParameter("username", username)
+ .uniqueResult();
+ }
+ }
+
+ public static User getByPublicKey(String publicKey) {
+ try (Session session = HibernateUtil.openSession()) {
+ return session.createQuery(
+ "from User where publicKey = :publicKey", User.class)
+ .setParameter("publicKey", publicKey)
+ .uniqueResult();
+ }
+ }
+
+ public static void addNotificationToken(String privateKey, String token) {
+ Session session = HibernateUtil.openSession();
+ Transaction transaction = null;
+ try {
+ transaction = session.beginTransaction();
+ User user = session.createQuery(
+ "from User where privateKey = :privateKey", User.class)
+ .setParameter("privateKey", privateKey)
+ .uniqueResult();
+ if (user != null) {
+ if (!user.notificationsTokens.contains(token)) {
+ user.notificationsTokens.add(token);
+ session.merge(user);
+ }
+ }
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw new RuntimeException("Error adding notification token: " + e.getMessage(), e);
+ } finally {
+ session.close();
+ }
+ }
+
+ public static void removeNotificationToken(String privateKey, String token) {
+ Session session = HibernateUtil.openSession();
+ Transaction transaction = null;
+ try {
+ transaction = session.beginTransaction();
+ User user = session.createQuery(
+ "from User where privateKey = :privateKey", User.class)
+ .setParameter("privateKey", privateKey)
+ .uniqueResult();
+ if (user != null) {
+ user.notificationsTokens = new ArrayList<>(user.notificationsTokens.stream()
+ .filter(t -> !t.equals(token))
+ .toList());
+ session.merge(user);
+ }
+ transaction.commit();
+ } catch (Exception e) {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ throw new RuntimeException("Error removing notification token: " + e.getMessage(), e);
+ } finally {
+ session.close();
+ }
+ }
+
+ public static List getNotificationTokensByPrivateKey(String privateKey) {
+ User user = getByPrivateKey(privateKey);
+ if (user != null) {
+ return user.notificationsTokens;
+ }
+ return List.of();
+ }
+
+ public static List getNotificationsTokensByPublicKey(String publicKey) {
+ User user = getByPublicKey(publicKey);
+ if (user != null) {
+ return user.notificationsTokens;
+ }
+ return List.of();
+ }
+}
diff --git a/src/main/resources/hibernate.cfg.xml b/src/main/resources/hibernate.cfg.xml
new file mode 100644
index 0000000..80bb79d
--- /dev/null
+++ b/src/main/resources/hibernate.cfg.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ org.postgresql.Driver
+ jdbc:postgresql://localhost:5432/rosetta_db
+ rosetta_user
+ rosetta_password
+ org.hibernate.dialect.PostgreSQLDialect
+ update
+ true
+ true
+
+
+
+
+