diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 6173d93..21303a4 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -2,7 +2,9 @@ package org.to.telegramfinalproject.Client; import org.json.JSONArray; import org.json.JSONObject; +import org.to.telegramfinalproject.Database.PrivateChatDatabase; import org.to.telegramfinalproject.Models.ChatEntry; +import org.to.telegramfinalproject.Models.ContactEntry; import org.to.telegramfinalproject.Models.SearchRequestModel; import org.to.telegramfinalproject.Models.SearchResultModel; @@ -25,6 +27,8 @@ public class ActionHandler { public static ActionHandler instance; + + private void handleRealTime(JSONObject json) throws IOException { IncomingMessageListener listener = new IncomingMessageListener(this.in); listener.handleRealTimeEvent (json); @@ -441,7 +445,8 @@ public class ActionHandler { JSONArray chatListJson = Session.currentUser.getJSONArray("chat_list"); JSONArray Archived = Session.currentUser.getJSONArray("archived_chat_list"); - JSONArray Active = Session.currentUser.getJSONArray("active_chat_lis"); + JSONArray Active = Session.currentUser.getJSONArray("active_chat_list"); + JSONArray contactList = Session.currentUser.getJSONArray("contact_list"); List chatList = new ArrayList<>(); @@ -457,9 +462,13 @@ public class ActionHandler { chat.isNull("last_message_time") ? null : LocalDateTime.parse(chat.getString("last_message_time")), chat.optBoolean("is_owner", false), chat.optBoolean("is_admin", false) + + ); - + if (chat.has("other_user_id")) { + entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); + } chatList.add(entry); @@ -479,6 +488,9 @@ public class ActionHandler { chat.optBoolean("is_owner", false), chat.optBoolean("is_admin", false) ); + if (chat.has("other_user_id")) { + entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); // šŸ‘ˆ اضافه کردن برای private chat + } archivedChats.add(entry); } @@ -497,11 +509,28 @@ public class ActionHandler { chat.optBoolean("is_owner", false), chat.optBoolean("is_admin", false) ); + if (chat.has("other_user_id")) { + entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); // šŸ‘ˆ اضافه کردن برای private chat + } activeChats.add(entry); } + Session.contactEntries.clear(); + for (Object obj : contactList) { + JSONObject c = (JSONObject) obj; + + ContactEntry entry = new ContactEntry( + UUID.fromString(c.getString("contact_id")), + c.getString("user_id"), + c.getString("profile_name"), + c.optString("image_url", ""), + c.optBoolean("is_blocked", false) + ); + Session.contactEntries.add(entry); + } + @@ -705,12 +734,14 @@ public class ActionHandler { System.out.println("\nšŸ”“ Messages fetched:"); System.out.println("─────────────────────────────────────────────"); for (int i = 0; i < messages.length(); i++) { + JSONObject m = messages.getJSONObject(i); String senderId = m.getString("sender_id"); + String senderName = m.optString("sender_name", "Other"); String content = m.getString("content"); String time = m.getString("send_at"); - String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : "Other"; + String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : senderName; System.out.println("[" + time + "] " + label + ": " + content); } System.out.println("─────────────────────────────────────────────"); @@ -747,7 +778,8 @@ public class ActionHandler { System.out.println("2. Search"); System.out.println("3. Create Channel"); System.out.println("4. Create group"); - System.out.println("5. Logout"); + System.out.println("5. View contacts"); + System.out.println("6. Logout"); System.out.print("Choose an option: "); String choice = scanner.nextLine(); @@ -760,7 +792,8 @@ public class ActionHandler { case "2" -> search(); case "3" -> createChannel(); case "4" -> createGroup(); - case "5" -> { + case "5" -> showContactList(); + case "6" -> { logout(); return; } @@ -769,6 +802,109 @@ public class ActionHandler { } } + public void showContactList() { + List contacts = Session.contactEntries; + if (contacts.isEmpty()) { + System.out.println("šŸ“­ You have no contacts."); + return; + } + + System.out.println("šŸ‘„ Your Contacts:"); + for (int i = 0; i < contacts.size(); i++) { + System.out.println((i + 1) + ". " + contacts.get(i)); + } + + System.out.print("Select a contact (0 to go back): "); + int choice = scanner.nextInt(); + scanner.nextLine(); + + if (choice == 0) return; + if (choice < 1 || choice > contacts.size()) { + System.out.println("āŒ Invalid choice."); + return; + } + + ContactEntry selected = contacts.get(choice - 1); + System.out.println("\nšŸ“‡ What do you want to do with " + selected.getProfileName() + "?"); + System.out.println("1. View Profile"); + System.out.println("2. Send Message"); + System.out.print("Enter your choice: "); + int action = scanner.nextInt(); + scanner.nextLine(); + + switch (action) { + case 1 -> viewProfile(selected.getContactId()); + case 2 -> startPrivateChat(selected); + default -> System.out.println("āŒ Invalid option."); + } + } + + private void viewProfile(UUID targetId) { + JSONObject req = new JSONObject(); + req.put("action", "view_profile"); + req.put("target_id", targetId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("āŒ Failed to fetch profile."); + return; + } + + JSONObject data = res.getJSONObject("data"); + String profileName = data.getString("profile_name"); + String userId = data.getString("user_id"); + String bio = data.optString("bio", "(no bio)"); + String imageUrl = data.optString("image_url", "(no image)"); + boolean isOnline = data.getBoolean("is_online"); + String lastSeen = data.getString("last_seen"); + + System.out.println("\nšŸ“„ Profile Info:"); + System.out.println("Name: " + profileName); + System.out.println("User ID: " + userId); + System.out.println("Bio: " + bio); + System.out.println("Image: " + imageUrl); + System.out.println("Status: " + (isOnline ? "🟢 Online" : "šŸ”˜ Last seen at " + lastSeen)); + } + + + private void startPrivateChat(ContactEntry contact) { + UUID myId = UUID.fromString(Session.currentUser.getString("internal_uuid")); + UUID contactId = contact.getContactId(); + + JSONObject req = new JSONObject(); + req.put("action", "get_or_create_private_chat"); + req.put("user1", myId.toString()); + req.put("user2", contactId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("āŒ Failed to create or fetch private chat."); + return; + } + + JSONObject data = res.getJSONObject("data"); + UUID chatId = UUID.fromString(data.getString("chat_id")); + + ChatEntry entry = new ChatEntry( + chatId, + contact.getUserId(), + contact.getProfileName(), + contact.getImageUrl(), + "private", + null, + false, + false + ); + entry.setOtherUserId(contactId); + + Session.chatList.add(0, entry); // اضافه به Ų§ŁˆŁ„ Ł„ŪŒŲ³ŲŖ + System.out.println("āœ… Chat with " + contact.getProfileName() + " started."); + + openChat(entry); // šŸ‘ˆ Ł…Ų³ŲŖŁ‚ŪŒŁ… وارد چت ؓو (اختیاری) + } + + + // public void showChatListAndSelect() { // // @@ -874,10 +1010,54 @@ public class ActionHandler { private void openChat(ChatEntry chat) { + + //for private chats only + if (chat.getType().equalsIgnoreCase("private")) { + JSONObject reqTarget = new JSONObject(); + reqTarget.put("action", "get_private_chat_target"); + reqTarget.put("chat_id", chat.getId()); + + JSONObject resTarget = sendWithResponse(reqTarget); + if (resTarget == null || !resTarget.getString("status").equals("success")) { + System.out.println("āŒ Failed to fetch target user for private chat."); + return; + } + String otherUserId = resTarget.getJSONObject("data").getString("target_id"); + chat.setOtherUserId(UUID.fromString(otherUserId)); + } + + + JSONObject req = new JSONObject(); req.put("action", "get_messages"); - req.put("receiver_id", chat.getId()); + req.put("receiver_id",chat.getId()); req.put("receiver_type", chat.getType()); +// String type = chat.getType(); +// UUID chatId = chat.getId(); +// if (type.equals("private")) { +// UUID myId = UUID.fromString(Session.currentUser.getString("internal_uuid")); +// UUID otherUserId = chat.getId(); +// +// JSONObject getChatIdReq = new JSONObject(); +// getChatIdReq.put("action", "get_or_create_private_chat"); +// getChatIdReq.put("user1", myId.toString()); +// getChatIdReq.put("user2", otherUserId.toString()); +// +// JSONObject chatIdRes = sendWithResponse(getChatIdReq); +// if (chatIdRes == null || !chatIdRes.getString("status").equals("success")) { +// System.out.println("āŒ Failed to fetch private chat ID."); +// return; +// } +// +// Session.currentPrivateChatUserId = otherUserId; +// chatId = UUID.fromString(chatIdRes.getJSONObject("data").getString("chat_id")); +// chat.setId(String.valueOf(chatId)); +// +// } + + + + JSONObject res = sendWithResponse(req); if (res == null || !res.getString("status").equals("success")) { @@ -891,14 +1071,24 @@ public class ActionHandler { for (int i = 0; i < messages.length(); i++) { JSONObject m = messages.getJSONObject(i); String senderId = m.getString("sender_id"); + String senderName = m.optString("sender_name", "Other"); String content = m.getString("content"); String time = m.getString("send_at"); - String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : "Other"; + String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : senderName; System.out.println("[" + time + "] " + label + ": " + content); } System.out.println("─────────────────────────────────────────────"); + if (chat.getType().equals("private") && chat.getOtherUserId() == null) { + for (ContactEntry contact : Session.contactEntries) { + if (contact.getContactId().equals(chat.getId())) { + chat.setOtherUserId(contact.getContactId()); + break; + } + } + } + boolean stayInChat = true; @@ -950,9 +1140,24 @@ public class ActionHandler { return false; } + + JSONObject reqTarget = new JSONObject(); + reqTarget.put("action", "get_private_chat_target"); + reqTarget.put("chat_id", chat.getId()); + + JSONObject resTarget = sendWithResponse(reqTarget); + if (resTarget == null || !resTarget.getString("status").equals("success")) { + System.out.println("āŒ Failed to fetch target user for private chat."); + return false; + } + + String otherUserId = resTarget.getJSONObject("data").getString("target_id"); + chat.setOtherUserId(UUID.fromString(otherUserId)); + + JSONObject req = new JSONObject(); req.put("action", "view_profile"); - req.put("target_id", chat.getId()); // internal UUID + req.put("target_id", chat.getOtherUserId()); // internal UUID JSONObject res = sendWithResponse(req); @@ -982,20 +1187,20 @@ public class ActionHandler { String input = scanner.nextLine(); switch (input) { - case "1" -> sendMessageTo(chat.getId(), "private"); - case "2" -> toggleBlock(chat.getId()); + case "1" -> sendMessage(chat.getId(), "private"); + case "2" -> toggleBlock(chat.getOtherUserId()); case "3" -> { deleteChat(chat.getId(), false); - return false; + return true; } case "4" -> { deleteChat(chat.getId(), true); - return false; + return true; } case "5" -> { JSONObject reqProfile = new JSONObject(); reqProfile.put("action", "view_profile"); - reqProfile.put("target_id", chat.getId()); + reqProfile.put("target_id", chat.getOtherUserId()); JSONObject resProfile = sendWithResponse(reqProfile); @@ -1032,6 +1237,7 @@ public class ActionHandler { return true; } + private JSONObject getGroupPermissions(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "get_group_permissions"); @@ -1094,7 +1300,7 @@ public class ActionHandler { String input = scanner.nextLine(); switch (input) { - case "1" -> sendMessageTo(chat.getId(), "group"); + case "1" -> sendMessage(chat.getId(), "group"); case "2" -> viewGroupMembers(chat.getId()); case "3" -> { if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) @@ -1216,7 +1422,7 @@ public class ActionHandler { switch (input) { case "1" -> { if (isOwner || (isAdmin && perms.optBoolean("can_post", false))) { - sendMessageTo(chat.getId(), "channel"); + sendMessage(chat.getId(), "channel"); } else { System.out.println("āŒ You don't have permission to post."); } @@ -1808,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); @@ -1823,6 +2029,8 @@ public class ActionHandler { private void toggleBlock(UUID userId) { + + JSONObject req = new JSONObject(); req.put("action", "toggle_block"); req.put("user_id", Session.getUserUUID()); @@ -1985,31 +2193,7 @@ public class ActionHandler { - private void sendMessageTo(UUID id, String type) { - System.out.print("Enter message: "); - String text = scanner.nextLine().trim(); - if (text.isEmpty()) { - System.out.println("Message cannot be empty."); - return; - } - - JSONObject req = new JSONObject(); - req.put("action", "send_message"); - req.put("sender_id", Session.getUserUUID()); - req.put("receiver_id", id.toString()); - req.put("receiver_type", type); - req.put("text", text); - - JSONObject res = sendWithResponse(req); - if (res == null) return; - - if (res.getBoolean("success")) { - System.out.println("āœ… Message sent."); - } else { - System.out.println("āŒ Failed to send message."); - } - } private void addMemberToGroup(UUID groupId, UUID userId) { JSONObject req = new JSONObject(); @@ -2464,6 +2648,9 @@ public class ActionHandler { System.err.println("āŒ Invalid request: missing action."); return null; } + System.out.println("šŸ“¤ Sending request to server: " + request.toString(2)); + System.out.println("šŸ“¤ [sendWithResponse] Action: " + request.optString("action", "unknown") + ", Full: " + request.toString(2)); + String requestId = UUID.randomUUID().toString(); request.put("request_id", requestId); @@ -2527,13 +2714,22 @@ public class ActionHandler { return; } + System.out.println("\nšŸ’¬ Your Chats:"); + System.out.println("0. šŸ“¦ Archived chats"); + int index = 1; - for (ChatEntry chat : Session.chatList) { - System.out.printf("%d. [%s] %s (%s)\n", index++, chat.getType(), chat.getName(), chat.getDisplayId()); + for (ChatEntry chat : Session.activeChats) { + String time = (chat.getLastMessageTime() == null) + ? "No messages yet" + : chat.getLastMessageTime().toString(); + + System.out.printf("%d. [%s] %s (%s) - Last: %s\n", + index++, chat.getType(), chat.getName(), chat.getDisplayId(), time); } } + public static class ChatStateMonitor implements Runnable { private final PrintWriter out; @@ -2898,6 +3094,127 @@ public class ActionHandler { } +// public void sendMessage(UUID receiverId, String receiverType) { +// Scanner scanner = new Scanner(System.in); +// +// System.out.print("Enter your message: "); +// String content = scanner.nextLine(); +// +// System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE): "); +// String messageType = scanner.nextLine(); +// Set allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); +// while (!allowedTypes.contains(messageType.toUpperCase())) { +// System.out.println("āŒ Invalid message type. Try again (TEXT / IMAGE / VIDEO / FILE): "); +// messageType = scanner.nextLine(); +// } +// messageType = messageType.toUpperCase(); +// +// JSONArray attachmentsArray = new JSONArray(); +// +// System.out.print("Do you want to attach files? (yes/no): "); +// if (scanner.nextLine().equalsIgnoreCase("yes")) { +// while (true) { +// System.out.print("File URL: "); +// String fileUrl = scanner.nextLine(); +// +// System.out.print("File Type (IMAGE / VIDEO / FILE): "); +// String fileType = scanner.nextLine(); +// +// JSONObject fileJson = new JSONObject(); +// fileJson.put("file_url", fileUrl); +// fileJson.put("file_type", fileType); +// attachmentsArray.put(fileJson); +// +// System.out.print("Add another file? (yes/no): "); +// if (!scanner.nextLine().equalsIgnoreCase("yes")) { +// break; +// } +// } +// } +// +// +// +// JSONObject messageJson = new JSONObject(); +// messageJson.put("action", "send_message"); +// messageJson.put("receiver_type", receiverType); +// messageJson.put("content", content); +// messageJson.put("message_type", messageType); +// if (receiverType.equals("private")) { +// messageJson.put("receiver_user_id", receiverId.toString()); +// } else { +// messageJson.put("receiver_id", receiverId.toString()); +// } +// +// if (!attachmentsArray.isEmpty()) { +// messageJson.put("attachments", attachmentsArray); +// } +// +// JSONObject response = sendWithResponse(messageJson); +// if (response != null && response.getString("status").equals("success")) { +// System.out.println("āœ… Message sent successfully! ID: " + response.getJSONObject("data").getString("message_id")); +// } else { +// System.out.println("āŒ Failed to send message: " + (response != null ? response.getString("message") : "no response")); +// } +// +// } + + + + public void sendMessage(UUID chatId, String receiverType) { + Scanner scanner = new Scanner(System.in); + + System.out.print("Enter your message: "); + String content = scanner.nextLine(); + + System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE): "); + String messageType = scanner.nextLine().toUpperCase(); + Set allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE"); + while (!allowedTypes.contains(messageType)) { + System.out.print("āŒ Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE): "); + messageType = scanner.nextLine().toUpperCase(); + } + + JSONArray attachmentsArray = new JSONArray(); + System.out.print("Do you want to attach files? (yes/no): "); + if (scanner.nextLine().equalsIgnoreCase("yes")) { + while (true) { + System.out.print("File URL: "); + String fileUrl = scanner.nextLine(); + System.out.print("File Type (IMAGE / VIDEO / FILE): "); + String fileType = scanner.nextLine().toUpperCase(); + + JSONObject fileJson = new JSONObject(); + fileJson.put("file_url", fileUrl); + fileJson.put("file_type", fileType); + attachmentsArray.put(fileJson); + + System.out.print("Add another file? (yes/no): "); + if (!scanner.nextLine().equalsIgnoreCase("yes")) break; + } + } + + // šŸ”¹ فقط Ų§Ų±Ų³Ų§Ł„ Ł¾ŪŒŲ§Ł… ŲØŲ§ chat_id و receiver_type + JSONObject messageJson = new JSONObject(); + messageJson.put("action", "send_message"); + messageJson.put("receiver_type", receiverType); + messageJson.put("receiver_id", chatId.toString()); + messageJson.put("content", content); + messageJson.put("message_type", messageType); + + if (!attachmentsArray.isEmpty()) { + messageJson.put("attachments", attachmentsArray); + } + + JSONObject response = sendWithResponse(messageJson); + if (response != null && response.getString("status").equals("success")) { + System.out.println("āœ… Message sent! ID: " + response.getJSONObject("data").getString("message_id")); + } else { + System.out.println("āŒ Failed to send message: " + (response != null ? response.getString("message") : "No response")); + } + } + + + diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index e10e03e..5168ba9 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -107,16 +107,22 @@ public class IncomingMessageListener implements Runnable { } case "chat_updated" -> { - System.out.println("\nšŸ”„ Group/Channel info updated."); - new Thread(() -> { - try { - handleAdminRoleChanged(msg); - } catch (IOException e) { - e.printStackTrace(); - } - }).start(); + System.out.println("\nšŸ”„ Chat info updated."); + + if (msg.has("last_message_time")) { + updateLastMessageTime(msg); + } else { + new Thread(() -> { + try { + handleAdminRoleChanged(msg); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } } + case "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> { System.out.println("🧩 Detected admin/owner role change. Calling handler..."); new Thread(() -> { @@ -139,6 +145,68 @@ public class IncomingMessageListener implements Runnable { System.out.print(">> "); } + private void updateLastMessageTime(JSONObject msg) { + try { + UUID chatUUID = UUID.fromString(msg.getString("chat_id")); + String newTime = msg.optString("last_message_time", null); + + Session.chatList.stream() + .filter(chat -> chat.getId().equals(chatUUID)) + .findFirst() + .ifPresent(chat -> { + chat.setLastMessageTime(newTime); + System.out.println("āœ… Updated last message time for chat: " + chat.getDisplayId()); + }); + + Session.activeChats.stream() + .filter(chat -> chat.getId().equals(chatUUID)) + .findFirst() + .ifPresent(chat -> { + chat.setLastMessageTime(newTime); + System.out.println("āœ… Updated last message time for chat: " + chat.getDisplayId()); + }); + + Session.archivedChats.stream() + .filter(chat -> chat.getId().equals(chatUUID)) + .findFirst() + .ifPresent(chat -> { + chat.setLastMessageTime(newTime); + System.out.println("āœ… Updated last message time for chat: " + chat.getDisplayId()); + }); + + Session.chatList.sort((c1, c2) -> { + if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; + if (c1.getLastMessageTime() == null) return 1; + if (c2.getLastMessageTime() == null) return -1; + return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending + }); + Session.activeChats.sort((c1, c2) -> { + if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; + if (c1.getLastMessageTime() == null) return 1; + if (c2.getLastMessageTime() == null) return -1; + return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending + }); + Session.archivedChats.sort((c1, c2) -> { + if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; + if (c1.getLastMessageTime() == null) return 1; + if (c2.getLastMessageTime() == null) return -1; + return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending + }); + + + + + if (Session.inChatListMenu) { + ActionHandler.displayChatList(); + System.out.print("Select a chat by number: "); + } + + } catch (Exception e) { + System.out.println("āŒ Failed to update last message time: " + e.getMessage()); + } + } + + private void handleAdminRoleChanged(JSONObject data) throws IOException { String chatType = data.getString("chat_type"); String chatId = data.optString("group_id", data.optString("channel_id", data.optString("chat_id", null))); @@ -230,11 +298,26 @@ public class IncomingMessageListener implements Runnable { private void displayRealTimeMessage(String action, JSONObject msg) { switch (action) { case "new_message" -> { - System.out.println("\nšŸ”” New Message:"); - System.out.println("From: " + msg.getString("sender")); - System.out.println("Time: " + msg.getString("time")); - System.out.println("Content: " + msg.getString("content")); + System.out.println("\nšŸ”” New Message Received:"); + String senderName = msg.optString("sender_name", "Unknown"); + String content = msg.optString("content", "(empty)"); + String sendAt = msg.optString("send_at", "-"); + + String receiverId = msg.optString("receiver_id", ""); + String receiverType = msg.optString("receiver_type", ""); + + boolean isInCurrentChat = Session.inChatMenu && + Session.currentChatId != null && + Session.currentChatId.equals(receiverId); + + if (isInCurrentChat) { + System.out.println(senderName + ": " + content + " (" + sendAt + ")"); + } else { + System.out.println("šŸ’¬ Message from " + senderName + " in " + receiverType + " chat: " + content); + Session.forceRefreshChatList = true; + } } + case "message_edited" -> { System.out.println("\nāœļø Message Edited:"); System.out.println("ID: " + msg.getString("message_id")); diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index c49bb3d..6e5f658 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -3,7 +3,9 @@ package org.to.telegramfinalproject.Client; import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.ChatEntry; +import org.to.telegramfinalproject.Models.ContactEntry; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -15,7 +17,7 @@ public class Session { public static List chatList = new ArrayList<>(); public static List archivedChats = new ArrayList<>(); public static List activeChats = new ArrayList<>(); - + public static UUID currentPrivateChatUserId = null; public static volatile boolean forceRefreshChatList = false; public static volatile boolean backToChatList = false; public static boolean inChatListMenu = false; @@ -24,7 +26,7 @@ public class Session { public static volatile boolean refreshCurrentChatMenu = false; public static String currentChatId = null; public static ChatEntry currentChatEntry = null; - + public static List contactEntries = new ArrayList<>(); @@ -36,24 +38,65 @@ public class Session { throw new RuntimeException("āŒ No UUID found in currentUser!"); } +// public static void updateChatList(JSONArray chatArray) { +// chatList.clear(); +// for (int i = 0; i < chatArray.length(); i++) { +// JSONObject obj = chatArray.getJSONObject(i); +// ChatEntry entry = new ChatEntry( +// UUID.fromString(obj.getString("internal_id")), +// obj.optString("id", ""), // displayId +// obj.optString("name", ""), // name +// obj.optString("image_url", ""), +// obj.getString("type"), +// null, // last message time (if needed, parse it) +// obj.optBoolean("is_owner", false), +// obj.optBoolean("is_admin", false) +// ); +// entry.setPermissions(obj.optJSONObject("permissions")); +// chatList.add(entry); +// } +// } + + public static void updateChatList(JSONArray chatArray) { chatList.clear(); for (int i = 0; i < chatArray.length(); i++) { JSONObject obj = chatArray.getJSONObject(i); + + LocalDateTime lastMessageTime = null; + if (obj.has("last_message_time") && !obj.isNull("last_message_time")) { + String timeStr = obj.getString("last_message_time"); + if (!timeStr.isBlank()) { + lastMessageTime = LocalDateTime.parse(timeStr); + } + } + ChatEntry entry = new ChatEntry( UUID.fromString(obj.getString("internal_id")), obj.optString("id", ""), // displayId obj.optString("name", ""), // name obj.optString("image_url", ""), obj.getString("type"), - null, // last message time (if needed, parse it) + lastMessageTime, obj.optBoolean("is_owner", false), obj.optBoolean("is_admin", false) ); - entry.setPermissions(obj.optJSONObject("permissions")); // Ų§ŚÆŲ± permissions وجود داره + + entry.setPermissions(obj.optJSONObject("permissions")); chatList.add(entry); } + + chatList.sort((c1, c2) -> { + if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0; + if (c1.getLastMessageTime() == null) return 1; + if (c2.getLastMessageTime() == null) return -1; + return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); + }); + + activeChats = chatList.stream().filter(c -> !c.isArchived()).toList(); + archivedChats = chatList.stream().filter(ChatEntry::isArchived).toList(); } + public static List getChatList() { return chatList; } diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 22ef33b..fcdaf7c 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -1,5 +1,6 @@ package org.to.telegramfinalproject.Database; +import org.to.telegramfinalproject.Models.FileAttachment; import org.to.telegramfinalproject.Models.Message; import java.sql.*; @@ -12,41 +13,106 @@ import java.util.stream.Collectors; public class MessageDatabase { - public static void save(Message message) { - String sql = """ - INSERT INTO messages ( - message_id, sender_id, receiver_type, receiver_id, content, - message_type, file_url, send_at, status, - reply_to_id, is_edited, original_message_id, forwarded_by, forwarded_from - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """; + + + + public static boolean insertMessage(UUID messageId, UUID senderId, UUID receiverId, + String receiverType, String content, String messageType) { + String sql = "INSERT INTO messages (message_id, sender_id, receiver_type, receiver_id, content, message_type) " + + "VALUES (?, ?, ?, ?, ?, ?)"; try (Connection conn = ConnectionDb.connect(); - PreparedStatement stmt = conn.prepareStatement(sql)) { + PreparedStatement ps = conn.prepareStatement(sql)) { - stmt.setObject(1, message.getMessage_id()); - stmt.setObject(2, message.getSender_id()); - stmt.setString(3, message.getReceiver_type()); - stmt.setObject(4, message.getReceiver_id()); - stmt.setString(5, message.getContent()); - stmt.setString(6, message.getMessage_type()); - stmt.setString(7, message.getFile_url()); - stmt.setObject(8, message.getSend_at()); - stmt.setString(9, message.getStatus()); - stmt.setObject(10, message.getReply_to_id()); - stmt.setBoolean(11, message.isIs_edited()); - stmt.setObject(12, message.getOriginal_message_id()); - stmt.setObject(13, message.getForwarded_by()); - stmt.setObject(14, message.getForwarded_from()); + ps.setObject(1, messageId); + ps.setObject(2, senderId); + ps.setString(3, receiverType); + ps.setObject(4, receiverId); + ps.setString(5, content); + ps.setString(6, messageType); - stmt.executeUpdate(); + return ps.executeUpdate() > 0; } catch (SQLException e) { - System.err.println("āŒ Error saving message: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + + + public static boolean insertAttachments(UUID messageId, List attachments) { + String sql = "INSERT INTO message_attachments (attachment_id, message_id, file_url, file_type) " + + "VALUES (?, ?, ?, ?)"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + for (FileAttachment att : attachments) { + ps.setObject(1, UUID.randomUUID()); + ps.setObject(2, messageId); + ps.setString(3, att.getFileUrl()); + ps.setString(4, att.getFileType()); + ps.addBatch(); + } + + ps.executeBatch(); + return true; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static void markGloballyDeleted(UUID chatId) { + String sql = "UPDATE messages SET is_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(); @@ -100,7 +166,6 @@ public class MessageDatabase { UUID.fromString(rs.getString("receiver_id")), rs.getString("content"), rs.getString("message_type"), - rs.getString("file_url"), rs.getTimestamp("send_at").toLocalDateTime(), rs.getString("status"), rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null, @@ -118,6 +183,31 @@ public class MessageDatabase { return messages; } + public static List getAttachments(UUID messageId) { + List attachments = new ArrayList<>(); + String sql = "SELECT file_url, file_type FROM message_attachments WHERE message_id = ?"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, messageId); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + attachments.add(new FileAttachment( + rs.getString("file_url"), + rs.getString("file_type") + )); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return attachments; + } + + public static LocalDateTime getLastMessageTimeBetween(UUID user1, UUID user2, String type) { String sql = """ @@ -177,7 +267,6 @@ public class MessageDatabase { UUID.fromString(rs.getString("receiver_id")), rs.getString("content"), rs.getString("message_type"), - rs.getString("file_url"), rs.getTimestamp("send_at").toLocalDateTime(), rs.getString("status"), (UUID) rs.getObject("reply_to_id"), @@ -217,25 +306,18 @@ public class MessageDatabase { return result; } - public static List privateChatHistory(UUID user1, UUID user2) { + public static List privateChatHistory(UUID chatId) { List result = new ArrayList<>(); String sql = """ SELECT * FROM messages WHERE receiver_type = 'private' - AND ( - (sender_id = ? AND receiver_id = ?) - OR (sender_id = ? AND receiver_id = ?) - ) + AND receiver_id = ? ORDER BY send_at """; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { - stmt.setObject(1, user1); - stmt.setObject(2, user2); - stmt.setObject(3, user2); - stmt.setObject(4, user1); - + stmt.setObject(1, chatId); ResultSet rs = stmt.executeQuery(); while (rs.next()) { result.add(extractMessage(rs)); @@ -248,6 +330,7 @@ public class MessageDatabase { + public static List groupChatHistory(UUID groupId) { List result = new ArrayList<>(); String sql = """ @@ -360,4 +443,42 @@ public class MessageDatabase { } + public List getMessagesForPrivateChat(UUID user1, UUID user2) { + UUID chatId = PrivateChatDatabase.findChatIdByUsers(user1, user2); + if (chatId == null) return new ArrayList<>(); + return findByReceiver("private", chatId); + } + + public static List findByReceiver(String receiverType, UUID receiverId) { + List messages = new ArrayList<>(); + String sql = "SELECT * FROM messages WHERE receiver_type = ? AND receiver_id = ? ORDER BY send_at"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + ps.setString(1, receiverType); + ps.setObject(2, receiverId); + + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + UUID messageId = (UUID) rs.getObject("message_id"); + UUID senderId = (UUID) rs.getObject("sender_id"); + UUID recId = (UUID) rs.getObject("receiver_id"); + String type = rs.getString("receiver_type"); + String content = rs.getString("content"); + String messageType = rs.getString("message_type"); + LocalDateTime sendAt = rs.getTimestamp("send_at").toLocalDateTime(); + + Message message = new Message(messageId, senderId, recId, type, content, messageType, sendAt); + messages.add(message); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return messages; + } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java new file mode 100644 index 0000000..97ddbcf --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java @@ -0,0 +1,257 @@ +package org.to.telegramfinalproject.Database; + +import org.to.telegramfinalproject.Models.PrivateChat; + +import java.sql.*; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class PrivateChatDatabase { + + + public static List getMembers(UUID privateChatId) { + String sql = "SELECT user1_id, user2_id FROM private_chat WHERE chat_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, privateChatId); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + UUID user1 = (UUID) rs.getObject("user1_id"); + UUID user2 = (UUID) rs.getObject("user2_id"); + return new ArrayList<>(Arrays.asList(user1, user2)); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return new ArrayList<>(); + } + + + public static UUID findChatIdByUsers(UUID user1, UUID user2) { + UUID u1 = user1.compareTo(user2) < 0 ? user1 : user2; + UUID u2 = user1.compareTo(user2) < 0 ? user2 : user1; + + String sql = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + ps.setObject(1, u1); + ps.setObject(2, u2); + + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + return (UUID) rs.getObject("chat_id"); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + + public static UUID getOrCreateChat(UUID user1, UUID user2) { + UUID u1 = user1.compareTo(user2) < 0 ? user1 : user2; + UUID u2 = user1.compareTo(user2) < 0 ? user2 : user1; + + String select = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(select)) { + ps.setObject(1, u1); + ps.setObject(2, u2); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + return UUID.fromString(rs.getString("chat_id")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + UUID newChatId = UUID.randomUUID(); + String insert = "INSERT INTO private_chat(chat_id, user1_id, user2_id) VALUES (?, ?, ?)"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(insert)) { + ps.setObject(1, newChatId); + ps.setObject(2, u1); + ps.setObject(3, u2); + ps.executeUpdate(); + return newChatId; + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } + + + public static List findChatsOfUser(UUID userId) { + List chats = new ArrayList<>(); + String sql = "SELECT * FROM private_chat WHERE user1_id = ? OR user2_id = ?"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + ps.setObject(1, userId); + ps.setObject(2, userId); + + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + UUID chatId = (UUID) rs.getObject("chat_id"); + UUID user1 = (UUID) rs.getObject("user1_id"); + UUID user2 = (UUID) rs.getObject("user2_id"); + + chats.add(new PrivateChat(chatId, user1, user2)); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return chats; + } + + + public static UUID getOtherUserInChat(UUID chatId, UUID currentUserId) { + String sql = "SELECT user1_id, user2_id 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 = (UUID) rs.getObject("user1_id"); + UUID user2 = (UUID) rs.getObject("user2_id"); + return currentUserId.equals(user1) ? user2 : user1; + } + } catch (SQLException e) { + e.printStackTrace(); + } + 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 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 ps = conn.prepareStatement(sql)) { + ps.setObject(1, senderId); + ps.setObject(2, senderId); + ps.setObject(3, chatId); + int rows = ps.executeUpdate(); + System.out.println("āœ… clearDeletedFlag updated rows = " + rows); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + + + + +} diff --git a/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java b/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java index b634ce3..8176576 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java +++ b/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java @@ -13,6 +13,7 @@ public class ChatEntry { private String type; private LocalDateTime lastMessageTime; private boolean archived = false; + private UUID otherUser; private boolean isOwner = false; @@ -115,4 +116,28 @@ public class ChatEntry { this.archived = archived; } +// public void setLastMessageTime(String newTime) {this.lastMessageTime = LocalDateTime.parse(newTime); +// } + + + public void setLastMessageTime(String newTime) { + if (newTime == null || newTime.isBlank()) { + this.lastMessageTime = null; + return; + } + + try { + this.lastMessageTime = LocalDateTime.parse(newTime); + } catch (Exception e) { + System.out.println("āŒ Failed to parse lastMessageTime: " + newTime); + this.lastMessageTime = null; + } + } + + public void setOtherUserId(UUID otherId) {this.otherUser = otherId; + } + + public UUID getOtherUserId(){ + return otherUser; + } } diff --git a/src/main/java/org/to/telegramfinalproject/Models/ContactEntry.java b/src/main/java/org/to/telegramfinalproject/Models/ContactEntry.java new file mode 100644 index 0000000..c0e4341 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Models/ContactEntry.java @@ -0,0 +1,44 @@ +package org.to.telegramfinalproject.Models; + +import java.util.UUID; + +public class ContactEntry { + private UUID contactId; // internal UUID + private String userId; // public ID + private String profileName; + private String imageUrl; + private boolean isBlocked; + + public ContactEntry(UUID contactId, String userId, String profileName, String imageUrl, boolean isBlocked) { + this.contactId = contactId; + this.userId = userId; + this.profileName = profileName; + this.imageUrl = imageUrl; + this.isBlocked = isBlocked; + } + + public UUID getContactId() { + return contactId; + } + + public String getUserId() { + return userId; + } + + public String getProfileName() { + return profileName; + } + + public String getImageUrl() { + return imageUrl; + } + + public boolean isBlocked() { + return isBlocked; + } + + @Override + public String toString() { + return profileName + " (" + userId + ")" + (isBlocked ? " [Blocked]" : ""); + } +} diff --git a/src/main/java/org/to/telegramfinalproject/Models/FileAttachment.java b/src/main/java/org/to/telegramfinalproject/Models/FileAttachment.java new file mode 100644 index 0000000..430e457 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Models/FileAttachment.java @@ -0,0 +1,19 @@ +package org.to.telegramfinalproject.Models; + +public class FileAttachment { + private String fileUrl; + private String fileType; + + public FileAttachment(String fileUrl, String fileType) { + this.fileUrl = fileUrl; + this.fileType = fileType; + } + + public String getFileUrl() { + return fileUrl; + } + + public String getFileType() { + return fileType; + } +} diff --git a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java index a9ed199..1b97b58 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java +++ b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java @@ -54,7 +54,6 @@ public class JsonUtil { obj.put("receiver_id", message.getReceiver_id().toString()); obj.put("content", message.getContent()); obj.put("message_type", message.getMessage_type()); - obj.put("file_url", message.getFile_url()); obj.put("send_at", message.getSend_at().toString()); obj.put("status", message.getStatus()); obj.put("reply_to_id", message.getReply_to_id() != null ? message.getReply_to_id().toString() : JSONObject.NULL); diff --git a/src/main/java/org/to/telegramfinalproject/Models/Message.java b/src/main/java/org/to/telegramfinalproject/Models/Message.java index eb7688a..a3de400 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/Message.java +++ b/src/main/java/org/to/telegramfinalproject/Models/Message.java @@ -1,6 +1,7 @@ package org.to.telegramfinalproject.Models; import java.time.LocalDateTime; +import java.util.List; import java.util.UUID; public class Message { @@ -10,7 +11,6 @@ public class Message { private UUID receiver_id; private String content; private String message_type; // TEXT, IMAGE, FILE, ... - private String file_url; private LocalDateTime send_at; private String status; // SENT, DELIVERED, READ private UUID reply_to_id; @@ -18,9 +18,10 @@ public class Message { private UUID original_message_id; private UUID forwarded_by; private UUID forwarded_from; + private List attachments; public Message(UUID message_id, UUID sender_id, String receiver_type, UUID receiver_id, String content, - String message_type, String file_url, LocalDateTime send_at, String status, + String message_type, LocalDateTime send_at, String status, UUID reply_to_id, boolean is_edited, UUID original_message_id, UUID forwarded_by, UUID forwarded_from) { this.message_id = message_id; @@ -29,7 +30,6 @@ public class Message { this.receiver_id = receiver_id; this.content = content; this.message_type = message_type; - this.file_url = file_url; this.send_at = send_at; this.status = status; this.reply_to_id = reply_to_id; @@ -39,6 +39,16 @@ public class Message { this.forwarded_from = forwarded_from; } + public Message(UUID messageId, UUID senderId, UUID receiverId, String receiverType, String content, String messageType, LocalDateTime now) { + this.message_id = messageId; + this.sender_id = senderId; + this.receiver_id = receiverId; + this.receiver_type = receiverType; + this.content = content; + this.message_type = messageType; + this.send_at = now; + } + public UUID getMessage_id() { return message_id; @@ -85,14 +95,6 @@ public class Message { this.message_type = message_type; } - public String getFile_url() { - return file_url; - } - - public void setFile_url(String file_url) { - this.file_url = file_url; - } - public LocalDateTime getSend_at() { return send_at; } @@ -148,4 +150,11 @@ public class Message { public void setForwarded_from(UUID forwarded_from) { this.forwarded_from = forwarded_from; } + + public void setAttachments(List attachments) { + this.attachments = attachments; + } + public List getAttachments() { + return attachments; + } } diff --git a/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java b/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java index 372600b..17ca2a9 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java +++ b/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java @@ -5,17 +5,29 @@ 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; - public PrivateChat(UUID chat_id, UUID user1_id, UUID user2_id, LocalDateTime created_at){ + public PrivateChat(UUID chat_id, UUID user1_id, UUID user2_id){ this.chat_id = chat_id; this.user1_id =user1_id; this.user2_id =user2_id; 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 28eb1cf..fef8a04 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -103,38 +103,83 @@ public class ClientHandler implements Runnable { List activeChatList = new ArrayList<>(); + JSONArray contactList = new JSONArray(); for (Contact contact : contacts) { User target = userDatabase.findByInternalUUID(contact.getContact_id()); if (target == null) continue; - LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(user.getInternal_uuid(), target.getInternal_uuid(), "private"); -// chatList.add(new ChatEntry( -// target.getInternal_uuid(), // internal UUID -// target.getUser_id(), // public display ID + JSONObject c = new JSONObject(); + c.put("user_id", contact.getUser_id().toString()); + c.put("contact_id", contact.getContact_id().toString()); + c.put("is_blocked", contact.getIs_blocked()); + + c.put("profile_name", target.getProfile_name()); + c.put("image_url", target.getImage_url()); + + contactList.put(c); + } + + + +// for (Contact contact : contacts) { +// User target = userDatabase.findByInternalUUID(contact.getContact_id()); +// if (target == null) continue; +// LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(user.getInternal_uuid(), target.getInternal_uuid(), "private"); +// +//// chatList.add(new ChatEntry( +//// target.getInternal_uuid(), // internal UUID +//// target.getUser_id(), // public display ID +//// target.getProfile_name(), +//// target.getImage_url(), +//// "private", +//// last, +//// false, +//// false +//// )); +// +// UUID targetId = target.getInternal_uuid(); +// +// ChatEntry entry = new ChatEntry( +// targetId, +// target.getUser_id(), // target.getProfile_name(), // target.getImage_url(), // "private", // last, // false, // false -// )); +// ); +// +// +// +// } + + + + List privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid()); + for (PrivateChat chat : privateChats) { + UUID otherId = chat.getUser1_id().equals(currentUser.getInternal_uuid()) ? + chat.getUser2_id() : chat.getUser1_id(); + + User otherUser = userDatabase.findByInternalUUID(otherId); + if (otherUser == null) continue; + + LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private"); - UUID targetId = target.getInternal_uuid(); ChatEntry entry = new ChatEntry( - targetId, - target.getUser_id(), - target.getProfile_name(), - target.getImage_url(), + chat.getChat_id(), + otherUser.getUser_id(), + otherUser.getProfile_name(), + otherUser.getImage_url(), "private", - last, + lastMessageTime, false, false ); + entry.setOtherUserId(otherId); - - - if (archivedChatIds.contains(targetId)) { + if (archivedChatIds.contains(chat.getChat_id())) { archivedChatList.add(entry); chatList.add(entry); } else { @@ -144,6 +189,7 @@ public class ClientHandler implements Runnable { } + for (Group group : groups) { LocalDateTime last = MessageDatabase.getLastMessageTime(group.getInternal_uuid(), "group"); boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), user.getInternal_uuid()); @@ -240,7 +286,8 @@ public class ClientHandler implements Runnable { JSONObject userData = JsonUtil.userToJson(user); userData.put("chat_list", JsonUtil.chatListToJson(chatList)); userData.put("archived_chat_list", JsonUtil.chatListToJson(archivedChatList)); - userData.put("active_chat_lis", JsonUtil.chatListToJson(activeChatList)); + userData.put("active_chat_list", JsonUtil.chatListToJson(activeChatList)); + userData.put("contact_list", contactList); response = new ResponseModel("success", "Welcome " + user.getProfile_name(), userData); } break; @@ -605,33 +652,85 @@ public class ClientHandler implements Runnable { List contacts = ContactDatabase.getContacts(currentUser.getInternal_uuid()); List groups = GroupDatabase.getGroupsByUser(currentUser.getInternal_uuid()); List channels = ChannelDatabase.getChannelsByUser(currentUser.getInternal_uuid()); - + List archivedChatIds = ArchivedChatDatabase.getArchivedChats(currentUser.getInternal_uuid()); List chatList = new ArrayList<>(); + List archivedChatList = new ArrayList<>(); + List activeChatList = new ArrayList<>(); - for (Contact contact : contacts) { - User target = userDatabase.findByInternalUUID(contact.getContact_id()); - if (target == null) continue; - LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(currentUser.getInternal_uuid(), target.getInternal_uuid(), "private"); - chatList.add(new ChatEntry( - target.getInternal_uuid(), - target.getUser_id(), - target.getProfile_name(), - target.getImage_url(), + + + +// for (Contact contact : contacts) { +// User target = userDatabase.findByInternalUUID(contact.getContact_id()); +// if (target == null) continue; +// +// LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(currentUser.getInternal_uuid(), target.getInternal_uuid(), "private"); +// +// chatList.add(new ChatEntry( +// target.getInternal_uuid(), +// target.getUser_id(), +// target.getProfile_name(), +// target.getImage_url(), +// "private", +// last, +// false, +// false +// )); +// } +// + + List privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid()); + for (PrivateChat chat : privateChats) { + UUID otherId = chat.getUser1_id().equals(currentUser.getInternal_uuid()) ? + chat.getUser2_id() : chat.getUser1_id(); + + User otherUser = userDatabase.findByInternalUUID(otherId); + if (otherUser == null) continue; + + LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private"); + + + ChatEntry entry = new ChatEntry( + chat.getChat_id(), + otherUser.getUser_id(), + otherUser.getProfile_name(), + otherUser.getImage_url(), "private", - last, + lastMessageTime, false, false - )); + ); + entry.setOtherUserId(otherId); + + if (archivedChatIds.contains(chat.getChat_id())) { + archivedChatList.add(entry); + chatList.add(entry); + } else { + activeChatList.add(entry); + chatList.add(entry); + } } + for (Group group : groups) { LocalDateTime last = MessageDatabase.getLastMessageTime(group.getInternal_uuid(), "group"); boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), currentUser.getInternal_uuid()); boolean isAdmin = GroupDatabase.isAdmin(group.getInternal_uuid(), currentUser.getInternal_uuid()); - chatList.add(new ChatEntry( +// chatList.add(new ChatEntry( +// group.getInternal_uuid(), +// group.getGroup_id(), +// group.getGroup_name(), +// group.getImage_url(), +// "group", +// last, +// isOwner, +// isAdmin +// )); + + ChatEntry entry = new ChatEntry( group.getInternal_uuid(), group.getGroup_id(), group.getGroup_name(), @@ -640,7 +739,17 @@ public class ClientHandler implements Runnable { last, isOwner, isAdmin - )); + ); + + if (archivedChatIds.contains(group.getInternal_uuid())) { + archivedChatList.add(entry); + chatList.add(entry); + } else { + activeChatList.add(entry); + chatList.add(entry); + } + + } for (Channel channel : channels) { @@ -648,7 +757,18 @@ public class ClientHandler implements Runnable { boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), currentUser.getInternal_uuid()); boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), currentUser.getInternal_uuid()); - chatList.add(new ChatEntry( +// chatList.add(new ChatEntry( +// channel.getInternal_uuid(), +// channel.getChannel_id(), +// channel.getChannel_name(), +// channel.getImage_url(), +// "channel", +// last, +// isOwner, +// isAdmin +// )); + + ChatEntry entry = new ChatEntry( channel.getInternal_uuid(), channel.getChannel_id(), channel.getChannel_name(), @@ -657,17 +777,39 @@ public class ClientHandler implements Runnable { last, isOwner, isAdmin - )); + ); + + if (archivedChatIds.contains(channel.getInternal_uuid())) { + archivedChatList.add(entry); + chatList.add(entry); + } else { + activeChatList.add(entry); + chatList.add(entry); + } } + activeChatList.sort((a, b) -> { + if (a.getLastMessageTime() == null) return 1; + if (b.getLastMessageTime() == null) return -1; + return b.getLastMessageTime().compareTo(a.getLastMessageTime()); + }); + + archivedChatList.sort((a, b) -> { + if (a.getLastMessageTime() == null) return 1; + if (b.getLastMessageTime() == null) return -1; + return b.getLastMessageTime().compareTo(a.getLastMessageTime()); + }); + + chatList.sort((a, b) -> { if (a.getLastMessageTime() == null) return 1; if (b.getLastMessageTime() == null) return -1; return b.getLastMessageTime().compareTo(a.getLastMessageTime()); }); - JSONObject data = new JSONObject(); data.put("chat_list", JsonUtil.chatListToJson(chatList)); + data.put("archived_chat_list", JsonUtil.chatListToJson(archivedChatList)); + data.put("active_chat_list", JsonUtil.chatListToJson(activeChatList)); response = new ResponseModel("success", "Chat list updated.", data); break; @@ -1116,13 +1258,17 @@ public class ClientHandler implements Runnable { switch (receiverType) { case "private" -> { - User otherUser = new userDatabase().findByInternalUUID(UUID.fromString(receiverId)); - if (otherUser == null) { - response = new ResponseModel("error", "User not found."); + UUID chatId = UUID.fromString(receiverId); + List members = PrivateChatDatabase.getMembers(chatId); + if (!members.contains(currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You're not a member of this private chat."); break; } - messages = MessageDatabase.privateChatHistory(currentUser.getInternal_uuid(), otherUser.getInternal_uuid()); + + messages = MessageDatabase.privateChatHistory(chatId); } + + case "group" -> { Group group = GroupDatabase.findByInternalUUID(UUID.fromString(receiverId)); if (group == null) { @@ -1157,6 +1303,9 @@ public class ClientHandler implements Runnable { obj.put("receiver_type", m.getReceiver_type()); obj.put("content", m.getContent()); obj.put("send_at", m.getSend_at().toString()); + User senderUser = userDatabase.findByInternalUUID(m.getSender_id()); + String senderName = senderUser != null ? senderUser.getProfile_name() : "Unknown"; + obj.put("sender_name", senderName); messageArray.put(obj); } @@ -1283,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."); @@ -1827,8 +1983,51 @@ public class ClientHandler implements Runnable { break; } + case "send_message" : { + response = handleSendMessage(requestJson); + } + break; + case "get_or_create_private_chat": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } + + try { + UUID user1 = UUID.fromString(requestJson.getString("user1")); + UUID user2 = UUID.fromString(requestJson.getString("user2")); + + UUID chatId = PrivateChatDatabase.getOrCreateChat(user1, user2); + + JSONObject data = new JSONObject(); + data.put("chat_id", chatId.toString()); + response = new ResponseModel("success", "Private chat ID retrieved.", data); + } catch (Exception e) { + e.printStackTrace(); + response = new ResponseModel("error", "Invalid data or internal error."); + } + + break; + } + + + + case "get_private_chat_target": { + UUID chatId = UUID.fromString(requestJson.getString("chat_id")); + userId = currentUser.getInternal_uuid(); + + UUID targetId = PrivateChatDatabase.getOtherUserInChat(chatId, userId); + if (targetId == null) { + response = new ResponseModel("error", "Could not find other user."); + } else { + JSONObject data = new JSONObject(); + data.put("target_id", targetId.toString()); + response = new ResponseModel("success", "Target fetched.", data); + } + break; + } @@ -1888,4 +2087,129 @@ public class ClientHandler implements Runnable { } + + + private ResponseModel handleSendMessage(JSONObject json) { + + try { + if (currentUser == null) + return new ResponseModel("error", "Unauthorized. Please login first."); + + UUID messageId = UUID.randomUUID(); + 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"); + + boolean inserted = MessageDatabase.insertMessage(messageId, senderId, receiverId, receiverType, content, messageType); + if (!inserted) + return new ResponseModel("error", "Failed to insert message."); + + if (json.has("attachments")) { + JSONArray attachmentsArray = json.getJSONArray("attachments"); + List attachments = new ArrayList<>(); + + for (int i = 0; i < attachmentsArray.length(); i++) { + JSONObject attJson = attachmentsArray.getJSONObject(i); + attachments.add(new FileAttachment( + attJson.getString("file_url"), + attJson.getString("file_type") + )); + } + + boolean attInserted = MessageDatabase.insertAttachments(messageId, attachments); + if (!attInserted) + return new ResponseModel("error", "Message inserted but failed to attach files."); + } + + // Send real-time message + Message msg = new Message(messageId, senderId, receiverId, receiverType, content, messageType, LocalDateTime.now()); + List receivers = getReceiversForChat(receiverId, receiverType); + receivers.remove(senderId); + RealTimeEventDispatcher.sendNewMessage(msg, receivers); + + // Update chat list (last_message_time) + JSONObject chatUpdate = new JSONObject(); + chatUpdate.put("chat_id", receiverId.toString()); + chatUpdate.put("chat_type", receiverType); + chatUpdate.put("last_message_time", LocalDateTime.now().toString()); + + JSONObject chatPayload = new JSONObject(); + chatPayload.put("action", "chat_updated"); + chatPayload.put("data", chatUpdate); + + for (UUID receiver : receivers) { + RealTimeEventDispatcher.sendToUser(receiver, chatPayload); + } + + JSONObject data = new JSONObject(); + data.put("message_id", messageId.toString()); + return new ResponseModel("success", "Message sent successfully.", data); + + } catch (Exception e) { + e.printStackTrace(); + return new ResponseModel("error", "Exception occurred while sending message."); + } + } + + + private List getReceiversForChat(UUID receiverId, String receiverType) { + switch (receiverType) { + case "private": + return PrivateChatDatabase.getMembers(receiverId); + case "group": + return GroupDatabase.getMemberUUIDs(receiverId); + case "channel": + return ChannelDatabase.getSubscriberUUIDs(receiverId); + default: + return new ArrayList<>(); + } + } + + + 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", json.optBoolean("both", 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 diff --git a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java index 3c96120..c9de28d 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java +++ b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java @@ -3,6 +3,7 @@ package org.to.telegramfinalproject.Server; import org.json.JSONObject; import org.to.telegramfinalproject.Database.ChannelDatabase; import org.to.telegramfinalproject.Database.GroupDatabase; +import org.to.telegramfinalproject.Database.userDatabase; import org.to.telegramfinalproject.Models.Message; import org.to.telegramfinalproject.Models.User; @@ -151,7 +152,6 @@ public class RealTimeEventDispatcher { data.put("sender", sender.getUser_id()); data.put("receiver_type", msg.getReceiver_type()); data.put("receiver_id", msg.getReceiver_id()); - data.put("file_url", msg.getFile_url()); data.put("file_type", msg.getMessage_type()); // IMAGE, FILE, VIDEO... data.put("time", msg.getSend_at().toString()); @@ -305,4 +305,30 @@ public class RealTimeEventDispatcher { broadcastToUsers(affectedUsers, event); } + + public static void sendNewMessage(Message message, List receivers) { + JSONObject payload = new JSONObject(); + payload.put("action", "new_message"); + + JSONObject data = new JSONObject(); + data.put("id", message.getMessage_id().toString()); + data.put("sender_id", message.getSender_id().toString()); + data.put("receiver_id", message.getReceiver_id().toString()); + data.put("receiver_type", message.getReceiver_type()); + data.put("content", message.getContent()); + data.put("send_at", message.getSend_at().toString()); + + User sender = userDatabase.findByInternalUUID(message.getSender_id()); + if (sender != null) { + data.put("sender_name", sender.getProfile_name()); + } + + payload.put("data", data); + + for (UUID userId : receivers) { + sendToUser(userId, payload); + } + } + + }