diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 21303a4..e2a57cb 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -2959,7 +2959,7 @@ public class ActionHandler { } case "2" -> { addContact(chat.getId()); - refreshChatList(); + refreshContactList(); } default -> System.out.println("Back..."); } @@ -3213,6 +3213,59 @@ public class ActionHandler { } } + private void refreshContactList() { + JSONObject req = new JSONObject(); + req.put("action", "get_contact_list"); + req.put("user_id", Session.currentUser.getString("user_id")); + out.println(req.toString()); + + try { + JSONObject response = TelegramClient.responseQueue.take(); + + if (response != null && response.getString("status").equals("success")) { + if (response.has("data") && !response.isNull("data")) { + JSONObject data = response.getJSONObject("data"); + + if (!data.has("contact_list") || data.isNull("contact_list")) { + System.out.println("❌ contact_list not found in response data."); + return; + } + + JSONArray contactListJson = data.getJSONArray("contact_list"); + List contactList = new ArrayList<>(); + + for (Object obj : contactListJson) { + 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) + ); + + contactList.add(entry); + } + + Session.contactEntries = contactList; + System.out.println("✅ Contact list updated. Total: " + contactList.size()); + + } else { + System.out.println("⚠️ Response has no data object."); + } + } else { + if (response.has("message") && !response.isNull("message")) { + System.out.println("❌ Failed to refresh contact list: " + response.getString("message")); + } else { + System.out.println("❌ Failed to refresh contact list."); + } + } + } catch (Exception e) { + System.err.println("❌ Error during refreshContactList: " + e.getMessage()); + e.printStackTrace(); + } + } diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 5168ba9..ed83bf7 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -79,19 +79,19 @@ public class IncomingMessageListener implements Runnable { "update_group_or_channel", "chat_deleted", "blocked_by_user", "unblocked_by_user", "message_seen", "removed_from_group", "removed_from_channel", - "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> true; + "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated","created_private_chat" -> true; default -> false; }; } void handleRealTimeEvent(JSONObject response) throws IOException { String action = response.getString("action"); - JSONObject msg = response.getJSONObject("data"); + JSONObject msg = response.has("data") ? response.getJSONObject("data") : new JSONObject(); switch (action) { case "added_to_group", "added_to_channel", - "removed_from_group", "removed_from_channel", "chat_deleted" -> { + "removed_from_group", "removed_from_channel", "chat_deleted","created_private_chat" -> { System.out.println("🔄 Chat list changed. Updating..."); Session.forceRefreshChatList = true; System.out.println("🧪 Calling requestChatList() after being added"); @@ -135,10 +135,6 @@ public class IncomingMessageListener implements Runnable { } -// - - - default -> displayRealTimeMessage(action, msg); } diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index 6e5f658..a13892c 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -27,8 +27,7 @@ public class Session { public static String currentChatId = null; public static ChatEntry currentChatEntry = null; public static List contactEntries = new ArrayList<>(); - - + public static boolean inContactListMenu = false; public static String getUserUUID() { diff --git a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java index edcd83c..6df2953 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java @@ -1,6 +1,7 @@ package org.to.telegramfinalproject.Database; import org.to.telegramfinalproject.Models.Contact; +import org.to.telegramfinalproject.Models.ContactEntry; import org.to.telegramfinalproject.Models.User; import java.sql.*; @@ -268,5 +269,39 @@ public class ContactDatabase { } } + public static List getContactEntries(UUID userId) { + List entries = new ArrayList<>(); + + String sql = """ + SELECT c.contact_id, c.is_blocked, u.user_id, u.profile_name, u.image_url + FROM contacts c + JOIN users u ON c.contact_id = u.internal_uuid + WHERE c.user_id = ? + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, userId); + ResultSet rs = ps.executeQuery(); + + while (rs.next()) { + UUID contactId = UUID.fromString(rs.getString("contact_id")); + boolean isBlocked = rs.getBoolean("is_blocked"); + String userIdStr = rs.getString("user_id"); + String profileName = rs.getString("profile_name"); + String imageUrl = rs.getString("image_url"); + + ContactEntry entry = new ContactEntry(contactId, userIdStr, profileName, imageUrl, isBlocked); + entries.add(entry); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return entries; + } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 370c022..3cda09e 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -76,7 +76,7 @@ public class MessageDatabase { } public static void logDeletedMessagesFor(UUID chatId, UUID userId) { - List messages = MessageDatabase.privateChatHistory(chatId); + List messages = MessageDatabase.privateChatHistory(chatId, userId); for (Message message : messages) { if (!isMessageDeleted(message.getMessage_id(), userId)) { String sql = """ @@ -306,18 +306,45 @@ public class MessageDatabase { return result; } - public static List privateChatHistory(UUID chatId) { +// public static List privateChatHistory(UUID chatId) { +// List result = new ArrayList<>(); +// String sql = """ +// SELECT * FROM messages +// WHERE receiver_type = 'private' +// AND receiver_id = ? +// ORDER BY send_at +// """; +// +// try (Connection conn = ConnectionDb.connect(); +// PreparedStatement stmt = conn.prepareStatement(sql)) { +// stmt.setObject(1, chatId); +// ResultSet rs = stmt.executeQuery(); +// while (rs.next()) { +// result.add(extractMessage(rs)); +// } +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// return result; +// } + + public static List privateChatHistory(UUID chatId, UUID userId) { List result = new ArrayList<>(); String sql = """ - SELECT * FROM messages + SELECT * FROM messages WHERE receiver_type = 'private' - AND receiver_id = ? + AND receiver_id = ? + AND is_deleted_globally = FALSE + AND message_id NOT IN ( + SELECT message_id FROM deleted_messages WHERE user_id = ? + ) ORDER BY send_at """; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setObject(1, chatId); + stmt.setObject(2, userId); ResultSet rs = stmt.executeQuery(); while (rs.next()) { result.add(extractMessage(rs)); @@ -331,11 +358,39 @@ public class MessageDatabase { - public static List groupChatHistory(UUID groupId) { + +// public static List groupChatHistory(UUID groupId) { +// List result = new ArrayList<>(); +// String sql = """ +// SELECT * FROM messages +// WHERE receiver_type = 'group' AND receiver_id = ? +// ORDER BY send_at +// """; +// +// try (Connection conn = ConnectionDb.connect(); +// PreparedStatement stmt = conn.prepareStatement(sql)) { +// stmt.setObject(1, groupId); +// ResultSet rs = stmt.executeQuery(); +// while (rs.next()) { +// result.add(extractMessage(rs)); +// } +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// return result; +// } + + + public static List groupChatHistory(UUID groupId,UUID userId) { List result = new ArrayList<>(); String sql = """ - SELECT * FROM messages - WHERE receiver_type = 'group' AND receiver_id = ? + SELECT * FROM messages + WHERE receiver_type = 'group' + AND receiver_id = ? + AND is_deleted_globally = FALSE + AND message_id NOT IN ( + SELECT message_id FROM deleted_messages WHERE user_id = ? + ) ORDER BY send_at """; @@ -353,11 +408,39 @@ public class MessageDatabase { } - public static List channelChatHistory(UUID channelId) { + +// public static List channelChatHistory(UUID channelId) { +// List result = new ArrayList<>(); +// String sql = """ +// SELECT * FROM messages +// WHERE receiver_type = 'channel' AND receiver_id = ? +// ORDER BY send_at +// """; +// +// try (Connection conn = ConnectionDb.connect(); +// PreparedStatement stmt = conn.prepareStatement(sql)) { +// stmt.setObject(1, channelId); +// ResultSet rs = stmt.executeQuery(); +// while (rs.next()) { +// result.add(extractMessage(rs)); +// } +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// return result; +// } + + + public static List channelChatHistory(UUID channelId,UUID userId) { List result = new ArrayList<>(); String sql = """ - SELECT * FROM messages - WHERE receiver_type = 'channel' AND receiver_id = ? + SELECT * FROM messages + WHERE receiver_type = 'channel' + AND receiver_id = ? + AND is_deleted_globally = FALSE + AND message_id NOT IN ( + SELECT message_id FROM deleted_messages WHERE user_id = ? + ) ORDER BY send_at """; @@ -375,6 +458,7 @@ public class MessageDatabase { } + public static List searchMessagesInGroups(List groupIds, String keyword) { List result = new ArrayList<>(); if (groupIds.isEmpty()) return result; diff --git a/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java index 97ddbcf..6cae138 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java @@ -251,7 +251,28 @@ public class PrivateChatDatabase { } + public static UUID findChatBetween(UUID user1, UUID user2) { + String sql = """ + SELECT chat_id FROM private_chat + 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, user1); + ps.setObject(2, user2); + ps.setObject(3, user2); + ps.setObject(4, user1); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + return (UUID) rs.getObject("chat_id"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return null; + } } diff --git a/src/main/java/org/to/telegramfinalproject/Models/Contact.java b/src/main/java/org/to/telegramfinalproject/Models/Contact.java index 8e78344..d85ae00 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/Contact.java +++ b/src/main/java/org/to/telegramfinalproject/Models/Contact.java @@ -40,5 +40,4 @@ public class Contact { - } diff --git a/src/main/java/org/to/telegramfinalproject/Models/ContactEntry.java b/src/main/java/org/to/telegramfinalproject/Models/ContactEntry.java index c0e4341..cd1e7e6 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/ContactEntry.java +++ b/src/main/java/org/to/telegramfinalproject/Models/ContactEntry.java @@ -1,5 +1,7 @@ package org.to.telegramfinalproject.Models; +import org.json.JSONObject; + import java.util.UUID; public class ContactEntry { @@ -41,4 +43,16 @@ public class ContactEntry { public String toString() { return profileName + " (" + userId + ")" + (isBlocked ? " [Blocked]" : ""); } + + + public JSONObject toJson() { + JSONObject obj = new JSONObject(); + obj.put("contact_id", contactId.toString()); + obj.put("user_id", userId); + obj.put("profile_name", profileName); + obj.put("image_url", imageUrl); + obj.put("is_blocked", isBlocked); + return obj; + } + } diff --git a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java index 1b97b58..049d582 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java +++ b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java @@ -2,6 +2,7 @@ package org.to.telegramfinalproject.Models; import org.json.JSONArray; import org.json.JSONObject; +import org.to.telegramfinalproject.Database.userDatabase; import java.time.LocalDateTime; import java.util.List; @@ -9,20 +10,43 @@ import java.util.UUID; public class JsonUtil { public static JSONArray contactListToJson(List contacts) { - - JSONArray array = new JSONArray(); + for (Contact contact : contacts) { + UUID contactId = contact.getContact_id(); + User contactUser = userDatabase.findByInternalUUID(contactId); + if (contactUser == null) continue; + JSONObject obj = new JSONObject(); - obj.put("user_id", contact.getUser_id().toString()); - obj.put("contact_id", contact.getContact_id().toString()); + obj.put("contact_id", contactId.toString()); // UUID + obj.put("user_id", contactUser.getUser_id()); // public ID + obj.put("profile_name", contactUser.getProfile_name()); + obj.put("image_url", contactUser.getImage_url()); obj.put("is_blocked", contact.getIs_blocked()); - obj.put("added_at", contact.getAdd_at().toString()); + array.put(obj); } + return array; } + public static JSONArray contactEntryListToJson(List contactEntries) { + JSONArray array = new JSONArray(); + + for (ContactEntry entry : contactEntries) { + JSONObject obj = new JSONObject(); + obj.put("contact_id", entry.getContactId().toString()); + obj.put("user_id", entry.getUserId()); + obj.put("profile_name", entry.getProfileName()); + obj.put("image_url", entry.getImageUrl()); + obj.put("is_blocked", entry.isBlocked()); + array.put(obj); + } + + return array; + } + + diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index fef8a04..07bfa1c 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -486,6 +486,7 @@ public class ClientHandler implements Runnable { currentUser.getImage_url(), contactUUID ); + } response = success @@ -1265,7 +1266,7 @@ public class ClientHandler implements Runnable { break; } - messages = MessageDatabase.privateChatHistory(chatId); + messages = MessageDatabase.privateChatHistory(chatId, currentUser.getInternal_uuid()); } @@ -1275,7 +1276,7 @@ public class ClientHandler implements Runnable { response = new ResponseModel("error", "Group not found."); break; } - messages = MessageDatabase.groupChatHistory(group.getInternal_uuid()); + messages = MessageDatabase.groupChatHistory(group.getInternal_uuid(),currentUser.getInternal_uuid()); } @@ -1285,7 +1286,7 @@ public class ClientHandler implements Runnable { response = new ResponseModel("error", "Channel not found."); break; } - messages = MessageDatabase.channelChatHistory(channel.getInternal_uuid()); + messages = MessageDatabase.channelChatHistory(channel.getInternal_uuid(),currentUser.getInternal_uuid()); } default -> { response = new ResponseModel("error", "Invalid receiver type."); @@ -1988,7 +1989,6 @@ public class ClientHandler implements Runnable { } break; - case "get_or_create_private_chat": { if (currentUser == null) { response = new ResponseModel("error", "Unauthorized. Please login first."); @@ -1999,7 +1999,23 @@ public class ClientHandler implements Runnable { UUID user1 = UUID.fromString(requestJson.getString("user1")); UUID user2 = UUID.fromString(requestJson.getString("user2")); + UUID oldChat = PrivateChatDatabase.findChatBetween(user1, user2); UUID chatId = PrivateChatDatabase.getOrCreateChat(user1, user2); + boolean isNew = oldChat == null; + + if (isNew) { + JSONObject chatPayload = new JSONObject(); + chatPayload.put("action", "created_private_chat"); + + JSONObject chatData = new JSONObject(); + chatData.put("chat_id", chatId.toString()); + chatData.put("chat_type", "private"); + chatData.put("last_message_time", LocalDateTime.now().toString()); + chatPayload.put("data", chatData); + + RealTimeEventDispatcher.sendToUser(user1, chatPayload); + RealTimeEventDispatcher.sendToUser(user2, chatPayload); + } JSONObject data = new JSONObject(); data.put("chat_id", chatId.toString()); @@ -2014,6 +2030,7 @@ public class ClientHandler implements Runnable { + case "get_private_chat_target": { UUID chatId = UUID.fromString(requestJson.getString("chat_id")); userId = currentUser.getInternal_uuid(); @@ -2029,6 +2046,43 @@ public class ClientHandler implements Runnable { break; } + case "get_contact_list": { + if (currentUser == null) { + response = new ResponseModel("error", "Unauthorized. Please login first."); + break; + } + + List contacts = ContactDatabase.getContacts(currentUser.getInternal_uuid()); + List contactEntries = new ArrayList<>(); + + for (Contact contact : contacts) { + UUID contactId = contact.getContact_id(); + User contactUser = userDatabase.findByInternalUUID(contactId); + if (contactUser == null) continue; + + ContactEntry entry = new ContactEntry( + contactId, + contactUser.getUser_id(), + contactUser.getProfile_name(), + contactUser.getImage_url(), + contact.getIs_blocked() + ); + + contactEntries.add(entry); + } + + // Optional: sort alphabetically + contactEntries.sort(Comparator.comparing(ContactEntry::getProfileName, String.CASE_INSENSITIVE_ORDER)); + + JSONObject data = new JSONObject(); + data.put("contact_list", JsonUtil.contactEntryListToJson(contactEntries)); + + response = new ResponseModel("success", "Contact list refreshed", data); + break; + } + + +