diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index a23588c..55f2bfc 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -1237,6 +1237,7 @@ public class ActionHandler { return true; } + private JSONObject getGroupPermissions(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "get_group_permissions"); @@ -2013,7 +2014,7 @@ public class ActionHandler { private void deleteChat(UUID targetId, boolean both) { JSONObject req = new JSONObject(); req.put("action", "delete_private_chat"); - req.put("target_id", targetId.toString()); + req.put("chat_id", targetId.toString()); req.put("both", both); send(req); diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 4b4e4b4..eea9f79 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -65,6 +65,54 @@ public class MessageDatabase { } } + public static void markGloballyDeleted(UUID chatId) { + String sql = "UPDATE messages SET deleted_globally = true WHERE receiver_id = ? AND receiver_type = 'private'"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, chatId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public static void logDeletedMessagesFor(UUID chatId, UUID userId) { + List messages = MessageDatabase.privateChatHistory(chatId); + for (Message message : messages) { + if (!isMessageDeleted(message.getMessage_id(), userId)) { + String sql = """ + INSERT INTO deleted_messages (message_id, user_id) + VALUES (?, ?) + ON CONFLICT (message_id, user_id) DO NOTHING + """; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, message.getMessage_id()); + ps.setObject(2, userId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + } + + public static boolean isMessageDeleted(UUID messageId, UUID userId) { + String sql = "SELECT 1 FROM deleted_messages WHERE message_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, messageId); + ps.setObject(2, userId); + ResultSet rs = ps.executeQuery(); + return rs.next(); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + + public void markMessageAsRead(UUID messageId, UUID userId) { String sql = "INSERT INTO message_receipts (message_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; try (Connection conn = ConnectionDb.connect(); diff --git a/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java index d96258f..7487b1f 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java @@ -2,10 +2,7 @@ package org.to.telegramfinalproject.Database; import org.to.telegramfinalproject.Models.PrivateChat; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -133,4 +130,129 @@ public class PrivateChatDatabase { return null; } + + private static PrivateChat extractPrivateChat(ResultSet rs) throws SQLException { + UUID chatId = UUID.fromString(rs.getString("chat_id")); + UUID user1 = rs.getObject("user1_id", UUID.class); + UUID user2 = rs.getObject("user2_id", UUID.class); + boolean user1Deleted = rs.getBoolean("user1_deleted"); + boolean user2Deleted = rs.getBoolean("user2_deleted"); + LocalDateTime createdAt = rs.getTimestamp("created_at").toLocalDateTime(); + + return new PrivateChat(chatId, user1, user2, user1Deleted, user2Deleted, createdAt); + } + + public static void markChatDeleted(UUID userId, UUID chatId) { + String sql = """ + UPDATE private_chat + SET user1_deleted = CASE WHEN user1_id = ? THEN true ELSE user1_deleted END, + user2_deleted = CASE WHEN user2_id = ? THEN true ELSE user2_deleted END + WHERE chat_id = ? + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, userId); + stmt.setObject(2, userId); + stmt.setObject(3, chatId); + stmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public static void unmarkChatDeleted(UUID userId, UUID chatId) { + String sql = """ + UPDATE private_chat + SET user1_deleted = CASE WHEN user1_id = ? THEN false ELSE user1_deleted END, + user2_deleted = CASE WHEN user2_id = ? THEN false ELSE user2_deleted END + WHERE chat_id = ? + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, userId); + stmt.setObject(2, userId); + stmt.setObject(3, chatId); + stmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public static void markUser1Deleted(UUID chatId) { + updateBoolField(chatId, "user1_deleted", true); + } + + public static void markUser2Deleted(UUID chatId) { + updateBoolField(chatId, "user2_deleted", true); + } + + public static void markBothDeleted(UUID chatId) { + String sql = "UPDATE private_chat SET user1_deleted = true, user2_deleted = true WHERE chat_id = ?"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, chatId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private static void updateBoolField(UUID chatId, String field, boolean value) { + String sql = "UPDATE private_chat SET " + field + " = ? WHERE chat_id = ?"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setBoolean(1, value); + ps.setObject(2, chatId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + + public static PrivateChat findById(UUID chatId) { + String sql = "SELECT * FROM private_chat WHERE chat_id = ?"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, chatId); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + UUID user1_id = (UUID) rs.getObject("user1_id"); + UUID user2_id = (UUID) rs.getObject("user2_id"); + boolean user1_deleted = rs.getBoolean("user1_deleted"); + boolean user2_deleted = rs.getBoolean("user2_deleted"); + LocalDateTime created_at = rs.getTimestamp("created_at").toLocalDateTime(); + + return new PrivateChat(chatId, user1_id, user2_id, user1_deleted, user2_deleted, created_at); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + + public static void clearDeletedFlag(UUID senderId, UUID receiverId) { + String sql = """ + UPDATE private_chat + SET user1_deleted = CASE WHEN user1_id = ? THEN false ELSE user1_deleted END, + user2_deleted = CASE WHEN user2_id = ? THEN false ELSE user2_deleted END + WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?) + """; + + try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, senderId); + ps.setObject(2, senderId); + ps.setObject(3, senderId); + ps.setObject(4, receiverId); + ps.setObject(5, receiverId); + ps.setObject(6, senderId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + + + } diff --git a/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java b/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java index 20caa6d..17ca2a9 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java +++ b/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java @@ -5,6 +5,8 @@ import java.util.UUID; public class PrivateChat { private final UUID chat_id; + private boolean user1_deleted; + private boolean user2_deleted; private UUID user1_id; private UUID user2_id; private LocalDateTime created_at; @@ -16,6 +18,16 @@ public class PrivateChat { this.created_at =created_at; } + public PrivateChat(UUID chatId, UUID user1, UUID user2, boolean user1Deleted, boolean user2Deleted, LocalDateTime createdAt) { + this.chat_id = chatId; + this.user1_id = user1; + this.user2_id = user2; + this.user1_deleted = user1Deleted; + this.user2_deleted = user2Deleted; + this.created_at = createdAt; + } + + public void setUser1_id(UUID user1_id){this.user1_id =user1_id;} public void setUser2_id(UUID user2_id){this.user2_id =user2_id;} public void setCreated_at(LocalDateTime created_at){this.created_at = created_at;} diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 5b611e2..49862fb 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1432,26 +1432,33 @@ public class ClientHandler implements Runnable { - case "delete_private_chat" : { + case "delete_private_chat": { if (currentUser == null) { response = new ResponseModel("error", "Unauthorized. Please login first."); break; } - UUID targetId = UUID.fromString(requestJson.getString("target_id")); + + // دریافت شناسه چت و نوع حذف (یک‌طرفه یا دوطرفه) + UUID targetId = UUID.fromString(requestJson.getString("chat_id")); boolean both = requestJson.getBoolean("both"); - //RealTime + // Real-Time Event Dispatch (اطلاع‌رسانی ریل تایم) if (both) { + // حذف دوطرفه RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid())); RealTimeEventDispatcher.notifyChatDeleted("private", currentUser.getInternal_uuid(), List.of(targetId)); } else { + // حذف یک‌طرفه RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid())); } - response = PrivateChatService.deletePrivateChat(currentUser.getInternal_uuid(), targetId, both); + + // فراخوانی متد حذف چت + response = handleDeleteChat(requestJson); break; } + case "get_group_permissions": { if (currentUser == null) { response = new ResponseModel("error", "Unauthorized. Please login first."); @@ -2025,8 +2032,6 @@ public class ClientHandler implements Runnable { - - default: response = new ResponseModel("error", "Unknown action: " + action); } @@ -2085,6 +2090,7 @@ public class ClientHandler implements Runnable { private ResponseModel handleSendMessage(JSONObject json) { + try { if (currentUser == null) return new ResponseModel("error", "Unauthorized. Please login first."); @@ -2093,9 +2099,13 @@ public class ClientHandler implements Runnable { UUID senderId = currentUser.getInternal_uuid(); String receiverType = json.getString("receiver_type"); UUID receiverId; - receiverId = UUID.fromString(json.getString("receiver_id")); + if(Objects.equals(receiverType, "private")){ + PrivateChatDatabase.clearDeletedFlag(senderId, receiverId); + } + + String content = json.optString("content", ""); String messageType = json.optString("message_type", "TEXT"); @@ -2165,5 +2175,42 @@ public class ClientHandler implements Runnable { } + private ResponseModel handleDeleteChat(JSONObject json) { + try { + if (currentUser == null) + return new ResponseModel("error", "Unauthorized"); + + UUID chatId = UUID.fromString(json.getString("chat_id")); + boolean bothSides = json.optBoolean("both_sides", false); + + PrivateChat chat = PrivateChatDatabase.findById(chatId); + if (chat == null) return new ResponseModel("error", "Chat not found."); + + UUID self = currentUser.getInternal_uuid(); + UUID other = chat.getUser1_id().equals(self) ? chat.getUser2_id() : chat.getUser1_id(); + + if (bothSides) { + PrivateChatDatabase.markBothDeleted(chatId); + MessageDatabase.markGloballyDeleted(chatId); + MessageDatabase.logDeletedMessagesFor(chatId, self); + MessageDatabase.logDeletedMessagesFor(chatId, other); + return new ResponseModel("success", "Chat deleted for both sides."); + } else { + if (chat.getUser1_id().equals(self)) + PrivateChatDatabase.markUser1Deleted(chatId); + else + PrivateChatDatabase.markUser2Deleted(chatId); + + MessageDatabase.logDeletedMessagesFor(chatId, self); + return new ResponseModel("success", "Chat deleted (one-sided)."); + } + + } catch (Exception e) { + e.printStackTrace(); + return new ResponseModel("error", "Exception while deleting chat."); + } + } + + } \ No newline at end of file