From fb19b5559374d65d82248754a4d0243e88b9a85f Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Thu, 12 Jun 2025 02:01:06 +0330 Subject: [PATCH 1/8] Work on add contact to contact list --- .../Client/ActionHandler.java | 242 +++++++++-------- .../Client/IncomingMessageListener.java | 86 ++++++ .../Client/TelegramClient.java | 9 +- .../Database/ChannelDatabase.java | 111 ++++++++ .../Database/ContactDatabase.java | 21 +- .../Database/GroupDatabase.java | 106 ++++++++ .../Database/MessageDatabase.java | 35 +++ .../Database/userDatabase.java | 1 - .../Models/ContactRequestModel.java | 23 ++ .../Models/SearchResultModel.java | 59 +++++ .../Server/ClientHandler.java | 72 +++-- .../Server/ContactService.java | 15 ++ .../Server/MainServer.java | 2 +- .../Server/RealTimeEventDispatcher.java | 248 ++++++++++++++++++ .../to/telegramfinalproject/Contact_Cell.fxml | 27 ++ .../org/to/telegramfinalproject/MainPage.fxml | 52 ++++ .../telegramfinalproject/Searching_view.fxml | 68 +++++ 17 files changed, 1035 insertions(+), 142 deletions(-) create mode 100644 src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java create mode 100644 src/main/java/org/to/telegramfinalproject/Models/ContactRequestModel.java create mode 100644 src/main/java/org/to/telegramfinalproject/Models/SearchResultModel.java create mode 100644 src/main/java/org/to/telegramfinalproject/Server/ContactService.java create mode 100644 src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java create mode 100644 src/main/resources/org/to/telegramfinalproject/Contact_Cell.fxml create mode 100644 src/main/resources/org/to/telegramfinalproject/MainPage.fxml create mode 100644 src/main/resources/org/to/telegramfinalproject/Searching_view.fxml diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 2555c2c..4ba057c 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -1,17 +1,17 @@ package org.to.telegramfinalproject.Client; +import org.json.JSONArray; +import org.json.JSONObject; +import org.to.telegramfinalproject.Models.ChatEntry; +import org.to.telegramfinalproject.Models.ContactRequestModel; +import org.to.telegramfinalproject.Models.SearchRequestModel; +import org.to.telegramfinalproject.Models.SearchResultModel; + import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.to.telegramfinalproject.Models.ChatEntry; -import org.to.telegramfinalproject.Models.SearchRequestModel; +import java.util.*; public class ActionHandler { private final PrintWriter out; @@ -26,41 +26,43 @@ public class ActionHandler { public void loginHandler() { System.out.println("Login form: \n"); - System.out.println("Username: "); + System.out.print("Username: "); String username = this.scanner.nextLine(); - System.out.println("Password: "); + System.out.print("Password: "); String password = this.scanner.nextLine(); + JSONObject request = new JSONObject(); request.put("action", "login"); request.put("user_id", JSONObject.NULL); request.put("username", username); request.put("password", password); request.put("profile_name", JSONObject.NULL); + this.send(request); } public void register() { System.out.println("Register form: \n"); - System.out.println("Username: "); + System.out.print("Username: "); String username = this.scanner.nextLine(); - System.out.println("User id: "); + System.out.print("User id: "); String user_id = this.scanner.nextLine(); - System.out.println("Password: "); + System.out.print("Password: "); String password = this.scanner.nextLine(); - System.out.println("Profile name: "); + System.out.print("Profile name: "); String profile_name = this.scanner.nextLine(); + JSONObject request = new JSONObject(); request.put("action", "register"); request.put("user_id", user_id); request.put("username", username); request.put("password", password); request.put("profile_name", profile_name); + this.send(request); } - - public void search(){ - + public void search() { System.out.print("Enter keyword to search: "); String keyword = scanner.nextLine(); @@ -71,10 +73,27 @@ public class ActionHandler { String userId = Session.currentUser.getString("user_id"); SearchRequestModel model = new SearchRequestModel("search", keyword, userId); - send(model.toJson()); } + private void addContact(UUID contactId) { + JSONObject req = new JSONObject(); + req.put("action", "add_contact"); + req.put("user_id", Session.currentUser.getString("user_id")); + req.put("contact_id", contactId.toString()); + send(req); + } + + + private void joinGroupOrChannel(String type, String id) { + JSONObject req = new JSONObject(); + req.put("action", "join_" + type); + req.put("user_id", Session.currentUser.getString("user_id")); + req.put("id", id); + send(req); + } + + private void send(JSONObject request) { try { if (!request.has("action") || request.isNull("action")) { @@ -83,113 +102,122 @@ public class ActionHandler { } String action = request.getString("action"); - this.out.println(request.toString()); String responseText = this.in.readLine(); - - if (responseText != null) { - JSONObject response = new JSONObject(responseText); - System.out.println("Server response: " + response.getString("message")); - String status = response.getString("status"); - - if (status.equals("success") && response.has("data") && !response.isNull("data")) { - switch (action) { - case "login": - case "register": - Session.currentUser = response.getJSONObject("data"); - JSONArray chatListJson = Session.currentUser.getJSONArray("chat_list"); - List chatList = new ArrayList<>(); - - for (Object obj : chatListJson) { - JSONObject chat = (JSONObject) obj; - ChatEntry entry = new ChatEntry( - chat.getString("id"), - chat.getString("name"), - chat.getString("image_url"), - chat.getString("type"), - chat.isNull("last_message_time") ? null : LocalDateTime.parse(chat.getString("last_message_time")) - ); - chatList.add(entry); - } - - Session.chatList = chatList; - break; - - case "search": - JSONArray results = response.getJSONObject("data").getJSONArray("results"); - - if (results.isEmpty()) { - System.out.println("No results found."); - } else { - System.out.println("\nSearch Results:"); - for (Object obj : results) { - JSONObject item = (JSONObject) obj; - if (item.getString("type").equals("message")) { - System.out.println("- [message] \"" + item.getString("content") + "\"" - + " (from: " + item.getString("sender") + ", at: " + item.getString("time") + ")"); - } else { - System.out.println("- [" + item.getString("type") + "] " - + item.getString("name") + " (ID: " + item.getString("id") + ")"); - } - - } - } - break; - - case "get_messages": - break; - } - } - - } else { + if (responseText == null) { System.out.println("No response from server."); + return; + } + + JSONObject response = new JSONObject(responseText); + System.out.println("Server response: " + response.getString("message")); + + String status = response.getString("status"); + if (!"success".equals(status) || !response.has("data") || response.isNull("data")) + return; + + switch (action) { + case "login": + case "register": + Session.currentUser = response.getJSONObject("data"); + JSONArray chatListJson = Session.currentUser.getJSONArray("chat_list"); + List chatList = new ArrayList<>(); + + for (Object obj : chatListJson) { + JSONObject chat = (JSONObject) obj; + ChatEntry entry = new ChatEntry( + chat.getString("id"), + chat.getString("name"), + chat.getString("image_url"), + chat.getString("type"), + chat.isNull("last_message_time") ? null : + LocalDateTime.parse(chat.getString("last_message_time")) + ); + chatList.add(entry); + } + + Session.chatList = chatList; + break; + + case "search": + JSONArray results = response.getJSONObject("data").getJSONArray("results"); + + if (results.isEmpty()) { + System.out.println("No results found."); + } else { + System.out.println("\nSearch Results:"); + for (int i = 0; i < results.length(); i++) { + JSONObject item = results.getJSONObject(i); + String type = item.getString("type"); + if (type.equals("message")) { + System.out.println((i + 1) + ". [message] \"" + item.getString("content") + "\"" + + " (from: " + item.optString("sender", "N/A") + ", at: " + item.getString("time") + ")"); + } else { + System.out.println((i + 1) + ". [" + type + "] " + + item.getString("name") + " (ID: " + item.getString("id") + ")"); + } + } + + System.out.print("Select a result number to interact: "); + int index = Integer.parseInt(scanner.nextLine()) - 1; + if (index < 0 || index >= results.length()) return; + + JSONObject selected = results.getJSONObject(index); + String type = selected.getString("type"); + switch (type) { + case "user" -> { + UUID contactId = UUID.fromString(selected.getString("uuid")); // ✅ درست + addContact(contactId); + } + + case "group", "channel" -> { + joinGroupOrChannel(selected.getString("type"), selected.getString("uuid")); // ✅ + } + + default -> System.out.println("No interaction available for type: " + type); + } + } + break; + + + + case "get_messages": + // Optional: handle later + break; } } catch (IOException e) { - System.err.println("Error while communicating with server: " + e.getMessage()); + System.err.println("Error communicating with server: " + e.getMessage()); } catch (Exception e) { System.err.println("Client error: " + e.getMessage()); e.printStackTrace(); } } - - - - public void userMenu() { + public void userMenu(UUID internal_uuid) { while (true) { System.out.println("\nUser Menu:"); System.out.println("1. Show chat list"); System.out.println("2. Search"); - System.out.println("3. Logout"); - + System.out.println("3. Add contact"); + System.out.println("4. Logout"); System.out.print("Choose an option: "); String choice = scanner.nextLine(); switch (choice) { - - case "1": - showChatListAndSelect(); - break; - case "2" : - search(); - break; - - case "3": - + case "1" -> showChatListAndSelect(); + case "2" -> search(); + case "3" -> addContact(internal_uuid); + case "4" -> { logout(); - return; - default: - System.out.println("Invalid choice."); + } + default -> System.out.println("Invalid choice."); } } } - - - public void showChatListAndSelect() { if (Session.chatList == null || Session.chatList.isEmpty()) { System.out.println("No chats available."); @@ -199,8 +227,11 @@ public class ActionHandler { System.out.println("\nYour Chats:"); for (int i = 0; i < Session.chatList.size(); i++) { ChatEntry entry = Session.chatList.get(i); - String time = entry.getLastMessageTime() == null ? "No messages yet" : entry.getLastMessageTime().toString(); - System.out.println((i + 1) + ". [" + entry.getType() + "] " + entry.getName() + " - Last: " + time); + String time = (entry.getLastMessageTime() == null) + ? "No messages yet" + : entry.getLastMessageTime().toString(); + System.out.println((i + 1) + ". [" + entry.getType() + "] " + + entry.getName() + " - Last: " + time); } System.out.print("Select a chat by number: "); @@ -215,24 +246,20 @@ public class ActionHandler { openChat(selected); } - private void openChat(ChatEntry chat) { JSONObject request = new JSONObject(); request.put("action", "get_messages"); request.put("receiver_id", chat.getId()); request.put("receiver_type", chat.getType()); - send(request); } - - public void logout() { if (Session.currentUser != null && Session.currentUser.has("user_id")) { JSONObject request = new JSONObject(); - String userId = Session.currentUser.getString("internalUUID"); request.put("action", "logout"); - request.put("user_id",userId); + request.put("user_id", Session.currentUser.getString("internalUUID")); + send(request); Session.currentUser = null; Session.chatList = null; @@ -241,5 +268,6 @@ public class ActionHandler { } } -} + +} diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java new file mode 100644 index 0000000..4de32f5 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -0,0 +1,86 @@ +package org.to.telegramfinalproject.Client; + + +import org.json.JSONObject; + +import java.io.BufferedReader; + +public class IncomingMessageListener implements Runnable { + private final BufferedReader in; + + public IncomingMessageListener(BufferedReader in) { + this.in = in; + } + + @Override + public void run() { + try { + String line; + while ((line = in.readLine()) != null) { + JSONObject response = new JSONObject(line); + + if (!response.has("action")) continue; + String action = response.getString("action"); + + switch (action) { + case "new_message" -> { + JSONObject msg = response.getJSONObject("data"); + 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.print(">> "); + } + + case "message_edited" -> { + JSONObject msg = response.getJSONObject("data"); + System.out.println("\n✏️ Message Edited:"); + System.out.println("ID: " + msg.getString("message_id")); + System.out.println("New Content: " + msg.getString("new_content")); + System.out.println("Edit Time: " + msg.getString("edit_time")); + System.out.print(">> "); + } + + case "message_deleted" -> { + JSONObject msg = response.getJSONObject("data"); + System.out.println("\n🗑️ Message Deleted:"); + System.out.println("Message ID: " + msg.getString("message_id")); + System.out.print(">> "); + } + + case "status_change" -> { + JSONObject msg = response.getJSONObject("data"); + System.out.println("\n🔄 User Status Changed:"); + System.out.println("User: " + msg.getString("user_id")); + System.out.println("Status: " + msg.getString("status")); + System.out.print(">> "); + } + + case "system_notification" -> { + JSONObject msg = response.getJSONObject("data"); + System.out.println("\n⚠️ System Notification:"); + System.out.println(msg.getString("content")); + System.out.print(">> "); + } + + case "contact_added" ->{ + System.out.println("\n🔔 You were added by a new contact: " + response.getString("user_id")); + System.out.print(">> "); + } + + + + default -> { + if (!action.equals("search")) { // ignore action: search + System.out.println("\n❓ Unknown action received: " + action); + System.out.print(">> "); + } + } + + } + } + } catch (Exception e) { + System.out.println("🔴 Listener stopped: " + e.getMessage()); + } + } +} diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index 6897bfc..ba614c0 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -8,10 +8,11 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; +import java.util.UUID; public class TelegramClient { private static final String SERVER_HOST = "localhost"; - private static final int SERVER_PORT = 12345; + private static final int SERVER_PORT = 8000; private Socket socket; private BufferedReader in; private PrintWriter out; @@ -51,7 +52,11 @@ public class TelegramClient { this.handler.loginHandler(); if (Session.currentUser != null) { System.out.println("Login successful."); - this.handler.userMenu(); + UUID internalId = UUID.fromString(Session.currentUser.getString("internalUUID")); + //Thread listenerThread = new Thread(new IncomingMessageListener(in)); + //listenerThread.setDaemon(true); + //listenerThread.start(); + this.handler.userMenu(internalId); } else { System.out.println("Login failed."); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index 8f8de92..426c2c0 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -75,4 +75,115 @@ public class ChannelDatabase { } return result; } + + + + public static List getSubscriberUUIDs(UUID channelInternalUUID) { + List subscriberIds = new ArrayList<>(); + String sql = "SELECT user_id FROM channel_subscribe WHERE channel_id = ?"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelInternalUUID); + + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + subscriberIds.add((UUID) rs.getObject("user_id")); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return subscriberIds; + } + + public static Channel findByChannelId(String channelId) { + String sql = "SELECT * FROM channels WHERE channel_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setString(1, channelId); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) { + Channel channel = new Channel(); + channel.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid"))); + channel.setChannel_id(rs.getString("channel_id")); + channel.setChannel_name(rs.getString("channel_name")); + channel.setImage_url(rs.getString("image_url")); + channel.setCreator_id(UUID.fromString(rs.getString("creator_id"))); + channel.setDescription(rs.getString("description")); + channel.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime()); + return channel; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + + + + public static boolean isUserSubscribed(UUID userId, UUID channelInternalId) { + String sql = "SELECT * FROM channel_subscribe WHERE user_id = ? AND channel_id = ?"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, userId); + stmt.setObject(2, channelInternalId); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + public static void addSubscriber(UUID channelInternalId, UUID userId) { + String sql = "INSERT INTO channel_subscribe (channel_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelInternalId); + stmt.setObject(2, userId); + stmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + + public static UUID findInternalUUIDByChannelId(String channelId) { + String sql = "SELECT internal_uuid FROM channels WHERE channel_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, channelId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return (UUID) rs.getObject("internal_uuid"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + + public static boolean addSubscriberToChannel(UUID userId, UUID channelUUID) { + String sql = """ + INSERT INTO channel_subscribers (channel_id, user_id) + VALUES (?, ?) + ON CONFLICT DO NOTHING + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelUUID); + stmt.setObject(2, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java index 6ca06ed..a890995 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java @@ -16,13 +16,17 @@ public class ContactDatabase { return ConnectionDb.connect(); } + public static boolean addContact(UUID userId, UUID contactId) { + String sql = """ + INSERT INTO contacts (user_id, contact_id) + VALUES (?, ?) + ON CONFLICT DO NOTHING + """; - public boolean addContact(UUID user_id, UUID contact_id) { - String sql = "INSERT INTO contacts (user_id, contact_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; - try (Connection connection = getConnection()) { - PreparedStatement stmt = connection.prepareStatement(sql); - stmt.setObject(1, user_id); - stmt.setObject(2,contact_id); + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, userId); + stmt.setObject(2, contactId); return stmt.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); @@ -31,6 +35,7 @@ public class ContactDatabase { } + public boolean removeContact(UUID user_id, UUID contact_id) { String sql = "DELETE FROM contacts WHERE user_id = ? AND contact_id = ?"; try (Connection connection = getConnection()) { @@ -94,7 +99,7 @@ public class ContactDatabase { } - public boolean existsContact(UUID user_id, UUID contact_id) { + public static boolean existsContact(UUID user_id, UUID contact_id) { String sql = "SELECT 1 FROM contacts WHERE user_id = ? AND contact_id = ? LIMIT 1"; // stop searching when find the first item in DB(LIMIT 1) try (Connection connection = getConnection()) { PreparedStatement stmt = connection.prepareStatement(sql); @@ -155,4 +160,6 @@ public class ContactDatabase { } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 79fa09b..b05abb5 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -76,5 +76,111 @@ public class GroupDatabase { return result; } + public static List getMemberUUIDs(UUID groupInternalUUID) { + List memberIds = new ArrayList<>(); + String sql = "SELECT user_id FROM group_members WHERE group_id = ?"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupInternalUUID); + + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + memberIds.add((UUID) rs.getObject("user_id")); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return memberIds; + } + + + + public static Group findByGroupId(String groupId) { + String sql = "SELECT * FROM groups WHERE group_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setString(1, groupId); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) { + Group group = new Group(); + group.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid"))); + group.setGroup_id(rs.getString("group_id")); + group.setGroup_name(rs.getString("group_name")); + group.setImage_url(rs.getString("image_url")); + group.setCreator_id(UUID.fromString(rs.getString("creator_id"))); + group.setDescription(rs.getString("description")); + group.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime()); + return group; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + + + public static boolean isUserInGroup(UUID userId, UUID groupInternalId) { + String sql = "SELECT * FROM group_members WHERE user_id = ? AND group_id = ?"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, userId); + stmt.setObject(2, groupInternalId); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + public static void addMember(UUID groupInternalId, UUID userId) { + String sql = "INSERT INTO group_members (group_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupInternalId); + stmt.setObject(2, userId); + stmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + public static UUID findInternalUUIDByGroupId(String groupId) { + String sql = "SELECT internal_uuid FROM groups WHERE group_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, groupId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return (UUID) rs.getObject("internal_uuid"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + + public static boolean addMemberToGroup(UUID userId, UUID groupUUID) { + String sql = """ + INSERT INTO group_members (group_id, user_id) + VALUES (?, ?) + ON CONFLICT DO NOTHING + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupUUID); + stmt.setObject(2, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 3f959eb..957b61d 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -12,6 +12,41 @@ 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = 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()); + + stmt.executeUpdate(); + + } catch (SQLException e) { + System.err.println("❌ Error saving message: " + e.getMessage()); + e.printStackTrace(); + } + } + 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/userDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java index 3427fa8..e5f09b1 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java @@ -228,7 +228,6 @@ public class userDatabase { } - public static User findByInternalUUID(UUID internalUuid) { String sql = "SELECT * FROM users WHERE internal_uuid = ?"; diff --git a/src/main/java/org/to/telegramfinalproject/Models/ContactRequestModel.java b/src/main/java/org/to/telegramfinalproject/Models/ContactRequestModel.java new file mode 100644 index 0000000..ac50e2d --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Models/ContactRequestModel.java @@ -0,0 +1,23 @@ +package org.to.telegramfinalproject.Models; + +import org.json.JSONObject; + +public class ContactRequestModel { + private String event; + private String contactId; + private String userId; + + public ContactRequestModel(String event, String contactId, String userId) { + this.event = event; + this.contactId = contactId; + this.userId = userId; + } + + public JSONObject toJson() { + JSONObject json = new JSONObject(); + json.put("event", event); + json.put("contact_id", contactId); + json.put("user_id", userId); + return json; + } +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Models/SearchResultModel.java b/src/main/java/org/to/telegramfinalproject/Models/SearchResultModel.java new file mode 100644 index 0000000..036a801 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Models/SearchResultModel.java @@ -0,0 +1,59 @@ +package org.to.telegramfinalproject.Models; + +public class SearchResultModel { + private final String type; + private final String id; // ← UUID واقعی برای عملیات + private final String displayId; // ← user_id یا group_id برای نمایش + private String name; + private final String content; + private final String sender; + private final String time; + + public SearchResultModel(String type, String id, String displayId, + String content, String sender, String time) { + this.type = type; + this.id = id; + this.displayId = displayId; + this.content = content; + this.sender = sender; + this.time = time; + } + + // فقط در صورت نیاز برای user/group/channel + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public String getId() { + return id; + } + + public String getDisplayId() { + return displayId; + } + + public String getName() { + return name; + } + + public String getContent() { + return content; + } + + public String getSender() { + return sender; + } + + public String getTime() { + return time; + } + + @Override + public String toString() { + return "[" + type.toUpperCase() + "] " + name + " (ID: " + displayId + ")"; + } +} diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 230ff53..adabffe 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -69,20 +69,12 @@ public class ClientHandler implements Runnable { } else { User user = authService.login(request.getUsername(), request.getPassword()); - - if (user == null){ + if (user == null) { response = new ResponseModel("error", "Login failed."); break; } this.currentUser = user; - if (SessionManager.contains(user.getInternal_uuid())) { - response = new ResponseModel("error", "You are already logged in from another device."); - break; - - } - - SessionManager.addUser(user.getInternal_uuid(), this.socket); userDatabase.updateUserStatus(user.getInternal_uuid(), "online"); List contacts = ContactDatabase.getContacts(user.getInternal_uuid()); @@ -126,21 +118,16 @@ public class ClientHandler implements Runnable { case "logout": { String user_Id = requestJson.optString("user_id"); - if (user_Id != null && !user_Id.isEmpty()) { - try { - UUID uuid = UUID.fromString(user_Id); - userDatabase.updateUserStatus(uuid, "offline"); - userDatabase.updateLastSeen(uuid); - SessionManager.removeUser(uuid); - response = new ResponseModel("success", "Logged out."); - } catch (IllegalArgumentException ex) { - response = new ResponseModel("error", "Invalid UUID format for user_id."); - } + if (userId != null && !user_Id.isEmpty()) { + UUID uuid = UUID.fromString(user_Id); + userDatabase.updateUserStatus(uuid, "offline"); + userDatabase.updateLastSeen(uuid); + SessionManager.removeUser(uuid); + response = new ResponseModel("success", "Logged out."); } else { response = new ResponseModel("error", "Invalid user_id for logout."); } break; - } case "search": { @@ -154,6 +141,7 @@ public class ClientHandler implements Runnable { JSONObject obj = new JSONObject(); obj.put("type", "user"); obj.put("id", u.getUser_id()); + obj.put("uuid", u.getInternal_uuid().toString()); // ✅ اضافه شود obj.put("name", u.getProfile_name()); results.add(obj); } @@ -161,7 +149,8 @@ public class ClientHandler implements Runnable { for (Group g : GroupDatabase.searchGroups(keyword)) { JSONObject obj = new JSONObject(); obj.put("type", "group"); - obj.put("id", g.getGroup_id()); + obj.put("id", g.getGroup_id()); // قابل نمایش + obj.put("uuid", g.getInternal_uuid().toString()); // برای عملیات obj.put("name", g.getGroup_name()); results.add(obj); } @@ -169,7 +158,8 @@ public class ClientHandler implements Runnable { for (Channel c : ChannelDatabase.searchChannels(keyword)) { JSONObject obj = new JSONObject(); obj.put("type", "channel"); - obj.put("id", c.getChannel_id()); + obj.put("id", c.getChannel_id()); // قابل نمایش + obj.put("uuid", c.getInternal_uuid().toString()); // برای عملیات obj.put("name", c.getChannel_name()); results.add(obj); } @@ -224,6 +214,40 @@ public class ClientHandler implements Runnable { break; } + case "add_contact": { + UUID userUUID = new userDatabase().findByUserId(requestJson.getString("user_id")).getInternal_uuid(); + UUID contactUUID = UUID.fromString(requestJson.getString("contact_id")); + + boolean success = ContactDatabase.addContact(userUUID, contactUUID); + response = success + ? new ResponseModel("success", "Contact added successfully.") + : new ResponseModel("error", "Failed to add contact. Maybe already exists."); + break; + } + + case "join_group": { + UUID userUUID = new userDatabase().findByUserId(requestJson.getString("user_id")).getInternal_uuid(); + UUID groupUUID = GroupDatabase.findInternalUUIDByGroupId(requestJson.getString("id")); + + boolean joined = GroupDatabase.addMemberToGroup(userUUID, groupUUID); + response = joined + ? new ResponseModel("success", "Joined group.") + : new ResponseModel("error", "Failed to join group."); + break; + } + + case "join_channel": { + UUID userUUID = new userDatabase().findByUserId(requestJson.getString("user_id")).getInternal_uuid(); + UUID channelUUID = ChannelDatabase.findInternalUUIDByChannelId(requestJson.getString("id")); + + boolean joined = ChannelDatabase.addSubscriberToChannel(userUUID, channelUUID); + response = joined + ? new ResponseModel("success", "Joined channel.") + : new ResponseModel("error", "Failed to join channel."); + break; + } + + default: response = new ResponseModel("error", "Unknown action: " + action); } @@ -236,7 +260,7 @@ public class ClientHandler implements Runnable { } } catch (IOException e) { System.out.println("Connection with client lost."); - userId = (currentUser != null) ? currentUser.getInternal_uuid() : SessionManager.getUserIdBySocket(this.socket); + userId = (currentUser != null) ? currentUser.getInternal_uuid() : SessionManager.getUserIdBySocket(this.socket); if (userId != null) { userDatabase.updateUserStatus(userId, "offline"); userDatabase.updateLastSeen(userId); @@ -261,4 +285,4 @@ public class ClientHandler implements Runnable { } -} +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/ContactService.java b/src/main/java/org/to/telegramfinalproject/Server/ContactService.java new file mode 100644 index 0000000..1c00835 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Server/ContactService.java @@ -0,0 +1,15 @@ +package org.to.telegramfinalproject.Server; + +import java.util.UUID; +import org.to.telegramfinalproject.Database.userDatabase; +import org.to.telegramfinalproject.Database.ContactDatabase; +public class ContactService { + public static boolean addContact(UUID userId, UUID contactId) { + if (userId.equals(contactId)) return false; + if (userDatabase.findByInternalUUID(contactId) == null) return false; + if (userDatabase.findByInternalUUID(userId) == null) return false; + if (ContactDatabase.existsContact(userId, contactId)) return false; + + return ContactDatabase.addContact(userId, contactId); + } +} diff --git a/src/main/java/org/to/telegramfinalproject/Server/MainServer.java b/src/main/java/org/to/telegramfinalproject/Server/MainServer.java index 5de2fbd..2318fa3 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/MainServer.java +++ b/src/main/java/org/to/telegramfinalproject/Server/MainServer.java @@ -7,7 +7,7 @@ import java.net.ServerSocket; import java.net.Socket; public class MainServer { - private static final int PORT = 12345; + private static final int PORT = 8000; public static void main(String[] args) { try (ServerSocket serverSocket = new ServerSocket(PORT)) { diff --git a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java new file mode 100644 index 0000000..d2a341f --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java @@ -0,0 +1,248 @@ +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.Models.Message; +import org.to.telegramfinalproject.Models.User; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.Socket; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +public class RealTimeEventDispatcher { + + public static void sendToUser(UUID userId, JSONObject data) { + Socket socket = SessionManager.getUserSocket(userId); + + + if (socket != null && !socket.isClosed()) { + try { + + + PrintWriter out = new PrintWriter(socket.getOutputStream(), true); + out.println(data.toString()); + } catch (IOException e) { + System.err.println("❌ Error sending to user: " + e.getMessage()); + } + + } + else { + System.out.println("⚠️ User " + userId + " is offline. Skipping real-time send."); + + } + } + + public static void broadcastToUsers(List userIds, JSONObject data) { + for (UUID userId : userIds) { + sendToUser(userId, data); + } + } + + public static JSONObject buildEvent(String action, JSONObject payload) { + JSONObject json = new JSONObject(); + json.put("action", action); + json.put("data", payload); + return json; + } + + + public static void notifyNewMessage(Message msg, User sender) { + JSONObject data = new JSONObject(); + data.put("sender", sender.getUser_id()); + data.put("receiver_type", msg.getReceiver_type()); + data.put("receiver_id", msg.getReceiver_id()); + data.put("content", msg.getContent()); + data.put("time", msg.getSend_at().toString()); + + JSONObject event = buildEvent("new_message", data); + + switch (msg.getReceiver_type()) { + case "private" -> RealTimeEventDispatcher.sendToUser(msg.getReceiver_id(), event); + case "group" -> { + List memberIds = GroupDatabase.getMemberUUIDs(msg.getReceiver_id()); + memberIds.remove(sender.getInternal_uuid()); + RealTimeEventDispatcher.broadcastToUsers(memberIds, event); + } + case "channel" -> { + List subscriberIds = ChannelDatabase.getSubscriberUUIDs(msg.getReceiver_id()); + subscriberIds.remove(sender.getInternal_uuid()); + subscriberIds.remove(sender.getInternal_uuid()); + RealTimeEventDispatcher.broadcastToUsers(subscriberIds, event); + } + } + } + + + + public static void notifyMessageEdited(UUID messageId, String newContent, List receivers) { + + String editTime = LocalDateTime.now().toString(); + JSONObject data = new JSONObject(); + data.put("message_id", messageId.toString()); + data.put("new_content", newContent); + data.put("edited_at", editTime); + + JSONObject event = new JSONObject(); + event.put("action", "edit_message"); + event.put("data", data); + + broadcastToUsers(receivers, event); + } + + + public static void notifyMessageDeleted(UUID messageId, List receivers) { + JSONObject data = new JSONObject(); + data.put("message_id", messageId.toString()); + + JSONObject event = new JSONObject(); + event.put("action", "delete_message"); + event.put("data", data); + + broadcastToUsers(receivers, event); + } + + public static void notifyUserUpdated(UUID userId, String newProfileName, String newImageUrl, List contactIds) { + JSONObject data = new JSONObject(); + data.put("user_id", userId.toString()); + data.put("new_name", newProfileName); + data.put("new_image_url", newImageUrl); + + JSONObject event = new JSONObject(); + event.put("action", "update_user"); + event.put("data", data); + + broadcastToUsers(contactIds, event); + } + + public static void notifyChatDeleted(String type, UUID id, List affectedUsers) { + JSONObject data = new JSONObject(); + data.put("chat_type", type); // private, group, channel + data.put("chat_id", id.toString()); + + JSONObject event = new JSONObject(); + event.put("action", "chat_deleted"); + event.put("data", data); + + broadcastToUsers(affectedUsers, event); + } + + public static void notifyGroupOrChannelUpdated(String type, UUID id, String newName, String newImageUrl, List affectedUsers) { + JSONObject data = new JSONObject(); + data.put("chat_type", type); // "group" or "channel" + data.put("chat_id", id.toString()); + data.put("new_name", newName); + data.put("new_image_url", newImageUrl); + + JSONObject event = new JSONObject(); + event.put("action", "update_group_or_channel"); + event.put("data", data); + + broadcastToUsers(affectedUsers, event); + } + + public static void notifyMediaMessage(Message msg, User sender) { + JSONObject data = new JSONObject(); + 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()); + + JSONObject event = new JSONObject(); + event.put("action", "new_media"); + event.put("data", data); + + switch (msg.getReceiver_type()) { + case "private" -> sendToUser(msg.getReceiver_id(), event); + case "group" -> { + List members = GroupDatabase.getMemberUUIDs(msg.getReceiver_id()); + members.remove(sender.getInternal_uuid()); + broadcastToUsers(members, event); + } + case "channel" -> { + List subs = ChannelDatabase.getSubscriberUUIDs(msg.getReceiver_id()); + subs.remove(sender.getInternal_uuid()); + broadcastToUsers(subs, event); + } + } + } + + public static void notifyAddedToChat(String type, UUID chatId, String chatName, String imageUrl, UUID userId) { + JSONObject data = new JSONObject(); + data.put("chat_type", type); // group یا channel + data.put("chat_id", chatId.toString()); + data.put("chat_name", chatName); + data.put("image_url", imageUrl); + + JSONObject event = new JSONObject(); + event.put("action", type.equals("group") ? "added_to_group" : "added_to_channel"); + event.put("data", data); + + sendToUser(userId, event); + } + + public static void notifyRemovedFromChat(String type, UUID chatId, UUID userId) { + JSONObject data = new JSONObject(); + data.put("chat_type", type); + data.put("chat_id", chatId.toString()); + + JSONObject event = new JSONObject(); + event.put("action", type.equals("group") ? "removed_from_group" : "removed_from_channel"); + event.put("data", data); + + sendToUser(userId, event); + } + public static void notifyMessageSeen(UUID messageId, UUID senderId) { + JSONObject data = new JSONObject(); + data.put("message_id", messageId.toString()); + data.put("seen_at", LocalDateTime.now().toString()); + + JSONObject event = new JSONObject(); + event.put("action", "message_seen"); + event.put("data", data); + + sendToUser(senderId, event); + } + + + public static void notifyBlocked(UUID blockerId, UUID blockedUserId) { + JSONObject data = new JSONObject(); + data.put("blocker_id", blockerId.toString()); + + JSONObject event = new JSONObject(); + event.put("action", "blocked_by_user"); + event.put("data", data); + + sendToUser(blockedUserId, event); + } + + + public static void notifyUnblocked(UUID unblockerId, UUID unblockedUserId) { + JSONObject data = new JSONObject(); + data.put("unblocker_id", unblockerId.toString()); + + JSONObject event = new JSONObject(); + event.put("action", "unblocked_by_user"); + event.put("data", data); + + sendToUser(unblockedUserId, event); + } + + public static void notifyUserStatusChanged(UUID userId, String status, List contacts) { + JSONObject data = new JSONObject(); + data.put("user_id", userId.toString()); + data.put("status", status); // online | offline + data.put("time", LocalDateTime.now().toString()); + + JSONObject event = buildEvent("user_status_changed", data); + + broadcastToUsers(contacts, event); + } + + +} diff --git a/src/main/resources/org/to/telegramfinalproject/Contact_Cell.fxml b/src/main/resources/org/to/telegramfinalproject/Contact_Cell.fxml new file mode 100644 index 0000000..7dd1d98 --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/Contact_Cell.fxml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/to/telegramfinalproject/MainPage.fxml b/src/main/resources/org/to/telegramfinalproject/MainPage.fxml new file mode 100644 index 0000000..a637fda --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/MainPage.fxml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 2b972c4b7cef7067bba670fd441989441a86656f Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Thu, 12 Jun 2025 12:25:28 +0330 Subject: [PATCH 2/8] Create groups an channels --- .../Client/ActionHandler.java | 193 +++++++++++++++++- .../telegramfinalproject/Client/Session.java | 8 + .../Database/ChannelDatabase.java | 86 +++++++- .../Database/GroupDatabase.java | 73 +++++++ .../telegramfinalproject/Models/JsonUtil.java | 12 ++ .../Server/ChannelService.java | 23 +++ .../Server/ClientHandler.java | 152 +++++++++++++- .../Server/GroupService.java | 23 +++ 8 files changed, 547 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/to/telegramfinalproject/Server/ChannelService.java create mode 100644 src/main/java/org/to/telegramfinalproject/Server/GroupService.java diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 4ba057c..e2db742 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -94,6 +94,117 @@ public class ActionHandler { } + private ChatEntry fetchChatInfo(String receiverId, String receiverType) { + JSONObject req = new JSONObject(); + req.put("action", "get_chat_info"); + req.put("receiver_id", receiverId); + req.put("receiver_type", receiverType); + out.println(req.toString()); + + try { + String responseText = in.readLine(); + if (responseText != null) { + JSONObject response = new JSONObject(responseText); + if (response.getString("status").equals("success")) { + JSONObject data = response.getJSONObject("data"); + return new ChatEntry( + receiverId, + data.getString("name"), + data.optString("image_url", ""), + receiverType, + null + ); + } + } + } catch (Exception e) { + System.err.println("Error fetching chat info: " + e.getMessage()); + } + + return new ChatEntry(receiverId, "[Unknown " + receiverType + "]", "", receiverType, null); + } + + + private void refreshChatList() { + JSONObject req = new JSONObject(); + req.put("action", "get_chat_list"); + req.put("user_id", Session.currentUser.getString("user_id")); + out.println(req.toString()); + + try { + String responseText = in.readLine(); + if (responseText != null) { + JSONObject response = new JSONObject(responseText); + if (response.getString("status").equals("success")) { + JSONArray chatListJson = response.getJSONObject("data").getJSONArray("chat_list"); + List chatList = new ArrayList<>(); + + for (Object obj : chatListJson) { + JSONObject chat = (JSONObject) obj; + ChatEntry entry = new ChatEntry( + chat.getString("id"), + chat.getString("name"), + chat.optString("image_url", ""), + chat.getString("type"), + chat.isNull("last_message_time") + ? null + : LocalDateTime.parse(chat.getString("last_message_time")) + ); + chatList.add(entry); + } + + Session.chatList = chatList; + System.out.println("✅ Chat list updated."); + } + } + } catch (Exception e) { + System.err.println("❌ Failed to refresh chat list: " + e.getMessage()); + } + } + + + public void createGroup() { + System.out.print("Enter group ID: "); + String groupId = scanner.nextLine(); + System.out.print("Enter group name: "); + String groupName = scanner.nextLine(); + System.out.print("Enter image URL (optional): "); + String imageUrl = scanner.nextLine(); + + JSONObject req = new JSONObject(); + req.put("action", "create_group"); + req.put("user_id", Session.getUserUUID()); + req.put("group_id", groupId); // ID قابل نمایش + req.put("group_name", groupName); + req.put("image_url", imageUrl.isBlank() ? JSONObject.NULL : imageUrl); + + send(req); + } + + + + public void createChannel() { + System.out.print("Enter channel ID: "); + String channelId = scanner.nextLine(); + System.out.print("Enter channel name: "); + String channelName = scanner.nextLine(); + System.out.print("Enter image URL (optional): "); + String imageUrl = scanner.nextLine(); + + JSONObject req = new JSONObject(); + req.put("action", "create_channel"); + req.put("user_id", Session.getUserUUID()); + req.put("channel_id", channelId); + req.put("channel_name", channelName); + req.put("image_url", imageUrl.isBlank() ? JSONObject.NULL : imageUrl); + + send(req); + } + + + + + + private void send(JSONObject request) { try { if (!request.has("action") || request.isNull("action")) { @@ -129,7 +240,7 @@ public class ActionHandler { ChatEntry entry = new ChatEntry( chat.getString("id"), chat.getString("name"), - chat.getString("image_url"), + chat.optString("image_url", ""), chat.getString("type"), chat.isNull("last_message_time") ? null : LocalDateTime.parse(chat.getString("last_message_time")) @@ -165,22 +276,86 @@ public class ActionHandler { JSONObject selected = results.getJSONObject(index); String type = selected.getString("type"); + switch (type) { case "user" -> { - UUID contactId = UUID.fromString(selected.getString("uuid")); // ✅ درست - addContact(contactId); + String userId = selected.getString("id"); + String uuid = selected.getString("uuid"); + + ChatEntry existing = Session.chatList.stream() + .filter(c -> c.getId().equals(userId) && c.getType().equals("private")) + .findFirst() + .orElse(null); + + if (existing != null) { + openChat(existing); + } else { + UUID contactId = UUID.fromString(uuid); + addContact(contactId); + ChatEntry newChat = fetchChatInfo(contactId.toString(), "private"); + refreshChatList(); + openChat(newChat); + } } case "group", "channel" -> { - joinGroupOrChannel(selected.getString("type"), selected.getString("uuid")); // ✅ + String id = selected.getString("id"); + String uuid = selected.getString("uuid"); + + ChatEntry existing = Session.chatList.stream() + .filter(c -> c.getId().equals(id) && c.getType().equals(type)) + .findFirst() + .orElse(null); + + if (existing != null) { + openChat(existing); + } else { + joinGroupOrChannel(type, uuid); + + ChatEntry newChat = fetchChatInfo(uuid, type); + refreshChatList(); + openChat(newChat); + } + } + + case "message" -> { + String receiverId = selected.getString("receiver_id"); + String receiverType = selected.getString("receiver_type"); + + ChatEntry chat = Session.chatList.stream() + .filter(c -> c.getId().equals(receiverId) && c.getType().equals(receiverType)) + .findFirst() + .orElseGet(() -> fetchChatInfo(receiverId, receiverType)); + + openChat(chat); } default -> System.out.println("No interaction available for type: " + type); } + } break; + case "create_group": + case "create_channel": + if (status.equals("success") && response.has("data")) { + JSONObject chatJson = response.getJSONObject("data"); + ChatEntry chat = new ChatEntry( + chatJson.getString("id"), + chatJson.getString("name"), + chatJson.optString("image_url", ""), + chatJson.getString("type"), + null + ); + refreshChatList(); + System.out.println("✅ Created and opening chat..."); + openChat(chat); + } + break; + + + case "get_messages": // Optional: handle later @@ -200,16 +375,18 @@ public class ActionHandler { System.out.println("\nUser Menu:"); System.out.println("1. Show chat list"); System.out.println("2. Search"); - System.out.println("3. Add contact"); - System.out.println("4. Logout"); + System.out.println("3. Create Channel"); + System.out.println("4. Create group"); + System.out.println("5. Logout"); System.out.print("Choose an option: "); String choice = scanner.nextLine(); switch (choice) { case "1" -> showChatListAndSelect(); case "2" -> search(); - case "3" -> addContact(internal_uuid); - case "4" -> { + case "3" -> createChannel(); + case "4" -> createGroup(); + case "5" -> { logout(); return; } diff --git a/src/main/java/org/to/telegramfinalproject/Client/Session.java b/src/main/java/org/to/telegramfinalproject/Client/Session.java index 9bf0d73..64270a1 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/Session.java +++ b/src/main/java/org/to/telegramfinalproject/Client/Session.java @@ -10,4 +10,12 @@ import java.util.List; public class Session { public static JSONObject currentUser; public static List chatList; + + public static String getUserUUID() { + if (currentUser.has("uuid")) return currentUser.getString("uuid"); + if (currentUser.has("internal_uuid")) return currentUser.getString("internal_uuid"); + if (currentUser.has("internalUUID")) return currentUser.getString("internalUUID"); + throw new RuntimeException("❌ No UUID found in currentUser!"); + } + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index 426c2c0..e6ca149 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -7,6 +7,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -139,16 +140,7 @@ public class ChannelDatabase { return false; } - public static void addSubscriber(UUID channelInternalId, UUID userId) { - String sql = "INSERT INTO channel_subscribe (channel_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; - try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { - stmt.setObject(1, channelInternalId); - stmt.setObject(2, userId); - stmt.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - } + public static UUID findInternalUUIDByChannelId(String channelId) { @@ -186,4 +178,78 @@ public class ChannelDatabase { } + public static boolean createChannel(Channel channel, UUID creatorId) { + String sql = """ + INSERT INTO channels ( + internal_uuid, channel_id, channel_name, + creator_id, image_url, description, created_at + ) + VALUES (gen_random_uuid(), ?, ?, ?, ?, ?, ?) + RETURNING internal_uuid + """; + + String subscriberSql = """ + INSERT INTO channel_subscribers (channel_id, user_id) VALUES (?, ?) + """; + + try (Connection conn = ConnectionDb.connect()) { + // مرحله اول: ساخت کانال + PreparedStatement stmt = conn.prepareStatement(sql); + stmt.setString(1, channel.getChannel_id()); + stmt.setString(2, channel.getChannel_name()); + stmt.setObject(3, creatorId); + stmt.setString(4, channel.getImage_url()); + stmt.setString(5, channel.getDescription()); + stmt.setObject(6, channel.getCreated_at()); + + ResultSet rs = stmt.executeQuery(); + if (!rs.next()) return false; + + UUID internalUUID = (UUID) rs.getObject("internal_uuid"); + channel.setInternal_uuid(internalUUID); // اختیاری برای پیگیری بعدی + + // مرحله دوم: افزودن کاربر به لیست سابسکرایبرها + PreparedStatement subStmt = conn.prepareStatement(subscriberSql); + subStmt.setObject(1, internalUUID); + subStmt.setObject(2, creatorId); + subStmt.executeUpdate(); + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean insertChannel(UUID internalUUID, String channelId, String channelName, UUID creatorId, String imageUrl, LocalDateTime createdAt) { + String sql = "INSERT INTO channels (internal_uuid, channel_id, channel_name, creator_id, image_url, created_at) VALUES (?, ?, ?, ?, ?, ?)"; + + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, internalUUID); + stmt.setString(2, channelId); + stmt.setString(3, channelName); + stmt.setObject(4, creatorId); + stmt.setString(5, imageUrl); + stmt.setObject(6, createdAt); + stmt.executeUpdate(); + return true; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static void addSubscriber(UUID channelId, UUID userId) { + String sql = "INSERT INTO channel_subscribers (channel_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; + + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + stmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index b05abb5..88fcae7 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -6,6 +6,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -182,5 +183,77 @@ public class GroupDatabase { return false; } } + public static boolean createGroup(Group group, UUID creatorId) { + String sql = """ + INSERT INTO groups ( + internal_uuid, group_id, group_name, + creator_id, image_url, description, created_at + ) + VALUES (gen_random_uuid(), ?, ?, ?, ?, ?, ?) + RETURNING internal_uuid + """; + + String memberSql = """ + INSERT INTO group_members (group_id, user_id) VALUES (?, ?) + """; + + try (Connection conn = ConnectionDb.connect()) { + PreparedStatement stmt = conn.prepareStatement(sql); + stmt.setString(1, group.getGroup_id()); + stmt.setString(2, group.getGroup_name()); + stmt.setObject(3, creatorId); + stmt.setString(4, group.getImage_url()); + stmt.setString(5, group.getDescription()); + stmt.setObject(6, group.getCreated_at()); + + ResultSet rs = stmt.executeQuery(); + if (!rs.next()) return false; + + UUID internalUUID = (UUID) rs.getObject("internal_uuid"); + group.setInternal_uuid(internalUUID); + + PreparedStatement memberStmt = conn.prepareStatement(memberSql); + memberStmt.setObject(1, internalUUID); + memberStmt.setObject(2, creatorId); + memberStmt.executeUpdate(); + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean insertGroup(UUID internalUUID, String groupId, String groupName, UUID creatorId, String imageUrl, LocalDateTime createdAt) { + String sql = "INSERT INTO groups (internal_uuid, group_id, group_name, creator_id, image_url, created_at) VALUES (?, ?, ?, ?, ?, ?)"; + + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, internalUUID); + stmt.setString(2, groupId); + stmt.setString(3, groupName); + stmt.setObject(4, creatorId); + stmt.setString(5, imageUrl); + stmt.setObject(6, createdAt); + stmt.executeUpdate(); + return true; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static void addMember(UUID groupId, UUID userId, String role) { + String sql = "INSERT INTO group_members (group_id, user_id, role) VALUES (?, ?, ?) ON CONFLICT DO NOTHING"; + + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + stmt.setString(3, role); + stmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java index 4e3a37f..f56159a 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java +++ b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java @@ -154,4 +154,16 @@ public class JsonUtil { } + public static JSONObject chatToJson(ChatEntry chat) { + JSONObject obj = new JSONObject(); + obj.put("id", chat.getId()); + obj.put("name", chat.getName()); + obj.put("image_url", chat.getImageUrl() != null ? chat.getImageUrl() : JSONObject.NULL); + obj.put("type", chat.getType()); + obj.put("last_message_time", chat.getLastMessageTime() != null ? chat.getLastMessageTime().toString() : JSONObject.NULL); + return obj; + } + + + } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ChannelService.java b/src/main/java/org/to/telegramfinalproject/Server/ChannelService.java new file mode 100644 index 0000000..a8f7a33 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Server/ChannelService.java @@ -0,0 +1,23 @@ +package org.to.telegramfinalproject.Server; + +import org.to.telegramfinalproject.Models.Channel; +import org.to.telegramfinalproject.Database.ChannelDatabase; + +import java.time.LocalDateTime; +import java.util.UUID; + +public class ChannelService { + public static boolean createChannel(String channelId, String channelName, UUID creatorUUID, String imageUrl) { + UUID internalUUID = UUID.randomUUID(); + LocalDateTime now = LocalDateTime.now(); + + boolean inserted = ChannelDatabase.insertChannel(internalUUID, channelId, channelName, creatorUUID, imageUrl, now); + + if (inserted) { + ChannelDatabase.addSubscriber(internalUUID, creatorUUID); // اضافه کردن سازنده + return true; + } + return false; + } + +} diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index adabffe..a6347ce 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -141,7 +141,7 @@ public class ClientHandler implements Runnable { JSONObject obj = new JSONObject(); obj.put("type", "user"); obj.put("id", u.getUser_id()); - obj.put("uuid", u.getInternal_uuid().toString()); // ✅ اضافه شود + obj.put("uuid", u.getInternal_uuid().toString()); obj.put("name", u.getProfile_name()); results.add(obj); } @@ -149,8 +149,8 @@ public class ClientHandler implements Runnable { for (Group g : GroupDatabase.searchGroups(keyword)) { JSONObject obj = new JSONObject(); obj.put("type", "group"); - obj.put("id", g.getGroup_id()); // قابل نمایش - obj.put("uuid", g.getInternal_uuid().toString()); // برای عملیات + obj.put("id", g.getGroup_id()); + obj.put("uuid", g.getInternal_uuid().toString()); obj.put("name", g.getGroup_name()); results.add(obj); } @@ -158,8 +158,8 @@ public class ClientHandler implements Runnable { for (Channel c : ChannelDatabase.searchChannels(keyword)) { JSONObject obj = new JSONObject(); obj.put("type", "channel"); - obj.put("id", c.getChannel_id()); // قابل نمایش - obj.put("uuid", c.getInternal_uuid().toString()); // برای عملیات + obj.put("id", c.getChannel_id()); + obj.put("uuid", c.getInternal_uuid().toString()); obj.put("name", c.getChannel_name()); results.add(obj); } @@ -171,6 +171,8 @@ public class ClientHandler implements Runnable { obj.put("content", m.getContent()); obj.put("sender", m.getSender_id().toString()); obj.put("time", m.getSend_at().toString()); + obj.put("receiver_id", m.getReceiver_id().toString()); + obj.put("receiver_type", m.getReceiver_type()); results.add(obj); } @@ -248,6 +250,146 @@ public class ClientHandler implements Runnable { } + case "get_chat_info": { + String id = requestJson.getString("receiver_id"); + String type = requestJson.getString("receiver_type"); + JSONObject data = new JSONObject(); + + switch (type) { + case "private" -> { + User u = new userDatabase().findByUserId(id); + if (u != null) { + data.put("name", u.getProfile_name()); + data.put("image_url", u.getImage_url()); + } else { + response = new ResponseModel("error", "User not found."); + break; + } + + } + case "group" -> { + Group g = GroupDatabase.findByGroupId(id); + if (g != null) { + data.put("name", g.getGroup_name()); + data.put("image_url", g.getImage_url()); + } else { + response = new ResponseModel("error", "Group not found."); + break; + } + } + case "channel" -> { + Channel c = ChannelDatabase.findByChannelId(id); + if (c != null) { + data.put("name", c.getChannel_name()); + data.put("image_url", c.getImage_url()); + } else { + response = new ResponseModel("error", "Channel not found."); + break; + } + } + + default -> { + response = new ResponseModel("error", "Unknown type."); + break; + } + } + + if (data.has("name")) { + response = new ResponseModel("success", "Chat info fetched", data); + } + + break; + } + + + case "get_chat_list": { + String userIdStr = requestJson.getString("user_id"); + User user = new userDatabase().findByUserId(userIdStr); + + List contacts = ContactDatabase.getContacts(user.getInternal_uuid()); + List groups = GroupDatabase.getGroupsByUser(user.getInternal_uuid()); + List channels = ChannelDatabase.getChannelsByUser(user.getInternal_uuid()); + + List chatList = new ArrayList<>(); + + 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.getUser_id(), target.getProfile_name(), target.getImage_url(), "private", last)); + } + + for (Group group : groups) { + LocalDateTime last = MessageDatabase.getLastMessageTime(group.getInternal_uuid(), "group"); + chatList.add(new ChatEntry(group.getGroup_id(), group.getGroup_name(), group.getImage_url(), "group", last)); + } + + for (Channel channel : channels) { + LocalDateTime last = MessageDatabase.getLastMessageTime(channel.getInternal_uuid(), "channel"); + chatList.add(new ChatEntry(channel.getChannel_id(), channel.getChannel_name(), channel.getImage_url(), "channel", last)); + } + + 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)); + + response = new ResponseModel("success", "Chat list updated.", data); + break; + } + case "create_group": { + try { + String groupId = requestJson.getString("group_id"); // ID نمایشی + String groupName = requestJson.getString("group_name"); + String userIdStr = requestJson.getString("user_id"); + String imageUrl = requestJson.optString("image_url", null); + + UUID creatorUUID = UUID.fromString(userIdStr); + + boolean created = GroupService.createGroup(groupId, groupName, creatorUUID, imageUrl); + + response = created + ? new ResponseModel("success", "Group created.") + : new ResponseModel("error", "Group creation failed."); + + } catch (Exception e) { + response = new ResponseModel("error", "Error creating group: " + e.getMessage()); + } + break; + } + + case "create_channel": { + try { + String channelId = requestJson.getString("channel_id"); + String channelName = requestJson.getString("channel_name"); + String userIdStr = requestJson.getString("user_id"); + String imageUrl = requestJson.optString("image_url", null); + + UUID creatorUUID = UUID.fromString(userIdStr); + + boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl); + + response = created + ? new ResponseModel("success", "Channel created.") + : new ResponseModel("error", "Channel creation failed."); + + } catch (Exception e) { + response = new ResponseModel("error", "Error creating channel: " + e.getMessage()); + } + break; + } + + + + + + + default: response = new ResponseModel("error", "Unknown action: " + action); } diff --git a/src/main/java/org/to/telegramfinalproject/Server/GroupService.java b/src/main/java/org/to/telegramfinalproject/Server/GroupService.java new file mode 100644 index 0000000..31da28b --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Server/GroupService.java @@ -0,0 +1,23 @@ +package org.to.telegramfinalproject.Server; + +import org.to.telegramfinalproject.Models.Group; +import org.to.telegramfinalproject.Database.GroupDatabase; + +import java.time.LocalDateTime; +import java.util.UUID; + +public class GroupService { + public static boolean createGroup(String groupId, String groupName, UUID creatorUUID, String imageUrl) { + UUID internalUUID = UUID.randomUUID(); + LocalDateTime now = LocalDateTime.now(); + + boolean inserted = GroupDatabase.insertGroup(internalUUID, groupId, groupName, creatorUUID, imageUrl, now); + + if (inserted) { + GroupDatabase.addMember(internalUUID, creatorUUID, "owner"); // سازنده owner می‌شود + return true; + } + return false; + } + +} From 5afe3662d4ed877d6a17d1f7368af96895b45499 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Thu, 12 Jun 2025 20:53:44 +0330 Subject: [PATCH 3/8] Fixed join channel and group bugs --- .../Client/ActionHandler.java | 10 ++++--- .../Database/ChannelDatabase.java | 27 ++++++++++++++++++ .../Database/GroupDatabase.java | 28 +++++++++++++++++++ .../telegramfinalproject/Models/JsonUtil.java | 21 +++++++++++--- .../Server/ClientHandler.java | 22 +++++++++++---- 5 files changed, 94 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index e2db742..25b639b 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -85,15 +85,16 @@ public class ActionHandler { } - private void joinGroupOrChannel(String type, String id) { + private void joinGroupOrChannel(String type, String uuid) { JSONObject req = new JSONObject(); req.put("action", "join_" + type); - req.put("user_id", Session.currentUser.getString("user_id")); - req.put("id", id); + req.put("user_id", Session.currentUser.getString("internalUUID")); // ✅ internal_uuid لازم است + req.put("id", uuid); // ✅ این هم internal_uuid است send(req); } + private ChatEntry fetchChatInfo(String receiverId, String receiverType) { JSONObject req = new JSONObject(); req.put("action", "get_chat_info"); @@ -312,12 +313,13 @@ public class ActionHandler { } else { joinGroupOrChannel(type, uuid); - ChatEntry newChat = fetchChatInfo(uuid, type); + ChatEntry newChat = fetchChatInfo(id, type); refreshChatList(); openChat(newChat); } } + case "message" -> { String receiverId = selected.getString("receiver_id"); String receiverType = selected.getString("receiver_type"); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index e6ca149..3e9783b 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -252,4 +252,31 @@ public class ChannelDatabase { } } + + public static Channel findByInternalUUID(UUID internalUUID) { + String sql = "SELECT * FROM channels WHERE internal_uuid = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, internalUUID); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) { + Channel channel = new Channel(); + channel.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid"))); + channel.setChannel_id(rs.getString("channel_id")); + channel.setChannel_name(rs.getString("channel_name")); + channel.setImage_url(rs.getString("image_url")); + channel.setCreator_id(UUID.fromString(rs.getString("creator_id"))); + channel.setDescription(rs.getString("description")); + channel.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime()); + return channel; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 88fcae7..1cbc98d 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -256,4 +256,32 @@ public class GroupDatabase { } } + + + public static Group findByInternalUUID(UUID internalUUID) { + String sql = "SELECT * FROM groups WHERE internal_uuid = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, internalUUID); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) { + Group group = new Group(); + group.setInternal_uuid(UUID.fromString(rs.getString("internal_uuid"))); + group.setGroup_id(rs.getString("group_id")); + group.setGroup_name(rs.getString("group_name")); + group.setImage_url(rs.getString("image_url")); + group.setCreator_id(UUID.fromString(rs.getString("creator_id"))); + group.setDescription(rs.getString("description")); + group.setCreated_at(rs.getTimestamp("created_at").toLocalDateTime()); + return group; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java index f56159a..eba775f 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java +++ b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java @@ -109,9 +109,13 @@ public class JsonUtil { - public static JSONArray groupMemberListToJson(List members) { JSONArray array = new JSONArray(); + + if (members == null) { + return array; + } + for (GroupMember m : members) { JSONObject obj = new JSONObject(); obj.put("group_id", m.getGroup_id().toString()); @@ -120,22 +124,31 @@ public class JsonUtil { obj.put("role", m.getRole()); array.put(obj); } + return array; } - public static JSONArray channelSubscribeToJson(List subscribes){ + + public static JSONArray channelSubscribeToJson(List subscribes) { JSONArray array = new JSONArray(); - for(ChannelSubscribe s :subscribes ){ + + if (subscribes == null) { + return array; + } + + for (ChannelSubscribe s : subscribes) { JSONObject obj = new JSONObject(); - obj.put("channel_id",s.getChannel_id().toString()); + obj.put("channel_id", s.getChannel_id().toString()); obj.put("user_id", s.getUser_id().toString()); obj.put("Subscribed_at", s.getJoin_at().toString()); + array.put(obj); } return array; } + public static JSONArray chatListToJson(List chatList) { JSONArray jsonArray = new JSONArray(); diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index a6347ce..8fe3943 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -228,10 +228,15 @@ public class ClientHandler implements Runnable { } case "join_group": { - UUID userUUID = new userDatabase().findByUserId(requestJson.getString("user_id")).getInternal_uuid(); - UUID groupUUID = GroupDatabase.findInternalUUIDByGroupId(requestJson.getString("id")); + UUID userUUID = UUID.fromString(requestJson.getString("user_id")); // مستقیم internalUUID دریافت می‌کنیم + Group group = GroupDatabase.findByInternalUUID(UUID.fromString(requestJson.getString("id"))); + if (group == null) { + response = new ResponseModel("error", "Group not found."); + break; + } - boolean joined = GroupDatabase.addMemberToGroup(userUUID, groupUUID); + + boolean joined = GroupDatabase.addMemberToGroup(userUUID, group.getInternal_uuid()); response = joined ? new ResponseModel("success", "Joined group.") : new ResponseModel("error", "Failed to join group."); @@ -239,10 +244,15 @@ public class ClientHandler implements Runnable { } case "join_channel": { - UUID userUUID = new userDatabase().findByUserId(requestJson.getString("user_id")).getInternal_uuid(); - UUID channelUUID = ChannelDatabase.findInternalUUIDByChannelId(requestJson.getString("id")); + UUID userUUID = UUID.fromString(requestJson.getString("user_id")); // مستقیم internalUUID دریافت می‌کنیم + Channel channel = ChannelDatabase.findByInternalUUID(UUID.fromString(requestJson.getString("id"))); + if (channel == null) { + response = new ResponseModel("error", "Channel not found."); + break; + } - boolean joined = ChannelDatabase.addSubscriberToChannel(userUUID, channelUUID); + + boolean joined = ChannelDatabase.addSubscriberToChannel(userUUID, channel.getInternal_uuid()); response = joined ? new ResponseModel("success", "Joined channel.") : new ResponseModel("error", "Failed to join channel."); From ee037532a1a2ddeb1d59dcb3be65f0f7a66663d5 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Mon, 30 Jun 2025 22:22:08 +0330 Subject: [PATCH 4/8] RealTime and openChat --- src/main/java/module-info.java | 2 + .../Client/ActionHandler.java | 906 ++++++++++++++++-- .../Client/EventProcessorThread.java | 20 + .../Client/IncomingMessageListener.java | 149 +-- .../Client/RealTimeBuffer.java | 12 + .../Client/TelegramClient.java | 83 +- .../Database/ChannelDatabase.java | 149 ++- .../Database/ContactDatabase.java | 114 ++- .../Database/GroupDatabase.java | 221 +++++ .../Database/MessageDatabase.java | 74 ++ .../Database/userDatabase.java | 2 +- .../Models/ChatEntry.java | 73 +- .../telegramfinalproject/Models/JsonUtil.java | 12 +- .../Models/PrivateChat.java | 2 +- .../Server/ChannelService.java | 2 +- .../Server/ClientHandler.java | 588 ++++++++++-- .../Server/GroupService.java | 5 +- .../Server/PrivateChatService.java | 22 + .../Server/RealTimeEventDispatcher.java | 2 + .../Utils/GroupPermissionUtil.java | 59 ++ 20 files changed, 2222 insertions(+), 275 deletions(-) create mode 100644 src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java create mode 100644 src/main/java/org/to/telegramfinalproject/Client/RealTimeBuffer.java create mode 100644 src/main/java/org/to/telegramfinalproject/Server/PrivateChatService.java create mode 100644 src/main/java/org/to/telegramfinalproject/Utils/GroupPermissionUtil.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f20151f..0024ec0 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -13,4 +13,6 @@ module org.to.telegramfinalproject { requires java.sql; opens org.to.telegramfinalproject to javafx.fxml; exports org.to.telegramfinalproject; + exports org.to.telegramfinalproject.Client; + opens org.to.telegramfinalproject.Client to javafx.fxml; } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 25b639b..ade0bfb 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -3,12 +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.ContactRequestModel; import org.to.telegramfinalproject.Models.SearchRequestModel; -import org.to.telegramfinalproject.Models.SearchResultModel; import java.io.BufferedReader; -import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; import java.util.*; @@ -39,6 +36,7 @@ public class ActionHandler { request.put("profile_name", JSONObject.NULL); this.send(request); + } public void register() { @@ -76,6 +74,20 @@ public class ActionHandler { send(model.toJson()); } + + public void searchInUsers(){ + System.out.println("Enter keyword to search: "); + String keyword = scanner.nextLine(); + if (Session.currentUser == null || !Session.currentUser.has("user_id")) { + System.out.println("You must be logged in to search."); + return; + } + String userId = Session.currentUser.getString("user_id"); + SearchRequestModel model = new SearchRequestModel("searchInUsers", keyword, userId); + send(model.toJson()); + + } + private void addContact(UUID contactId) { JSONObject req = new JSONObject(); req.put("action", "add_contact"); @@ -88,8 +100,8 @@ public class ActionHandler { private void joinGroupOrChannel(String type, String uuid) { JSONObject req = new JSONObject(); req.put("action", "join_" + type); - req.put("user_id", Session.currentUser.getString("internalUUID")); // ✅ internal_uuid لازم است - req.put("id", uuid); // ✅ این هم internal_uuid است + req.put("user_id", Session.getUserUUID()); + req.put("id", uuid); send(req); } @@ -103,25 +115,35 @@ public class ActionHandler { out.println(req.toString()); try { - String responseText = in.readLine(); - if (responseText != null) { - JSONObject response = new JSONObject(responseText); - if (response.getString("status").equals("success")) { - JSONObject data = response.getJSONObject("data"); - return new ChatEntry( - receiverId, - data.getString("name"), - data.optString("image_url", ""), - receiverType, - null - ); - } + JSONObject response = TelegramClient.responseQueue.take(); + if (response != null && response.getString("status").equals("success")) { + JSONObject data = response.getJSONObject("data"); + + return new ChatEntry( + UUID.fromString(data.getString("internal_id")), + receiverId, + data.getString("name"), + data.optString("image_url", ""), + receiverType, + null, + data.optBoolean("is_owner", false), + data.optBoolean("is_admin", false) + ); + } } catch (Exception e) { System.err.println("Error fetching chat info: " + e.getMessage()); } - return new ChatEntry(receiverId, "[Unknown " + receiverType + "]", "", receiverType, null); + // اگر شکست خورد، internalId نامعتبر می‌سازیم (برای جلوگیری از null) + return new ChatEntry( + UUID.randomUUID(), // ساخت یک UUID موقت (ولی اشتباه) + receiverId, + "[Unknown " + receiverType + "]", + "", + receiverType, + null + ); } @@ -132,37 +154,44 @@ public class ActionHandler { out.println(req.toString()); try { - String responseText = in.readLine(); - if (responseText != null) { - JSONObject response = new JSONObject(responseText); + JSONObject response = TelegramClient.responseQueue.take(); + if (response != null) { if (response.getString("status").equals("success")) { JSONArray chatListJson = response.getJSONObject("data").getJSONArray("chat_list"); List chatList = new ArrayList<>(); for (Object obj : chatListJson) { JSONObject chat = (JSONObject) obj; + ChatEntry entry = new ChatEntry( + UUID.fromString(chat.getString("internal_id")), chat.getString("id"), chat.getString("name"), chat.optString("image_url", ""), chat.getString("type"), - chat.isNull("last_message_time") - ? null - : LocalDateTime.parse(chat.getString("last_message_time")) + chat.isNull("last_message_time") ? null : LocalDateTime.parse(chat.getString("last_message_time")), + chat.optBoolean("is_owner", false), + chat.optBoolean("is_admin", false) ); + chatList.add(entry); } Session.chatList = chatList; System.out.println("✅ Chat list updated."); + } else { + System.out.println("❌ Failed to refresh chat list: " + response.getString("message")); } } } catch (Exception e) { - System.err.println("❌ Failed to refresh chat list: " + e.getMessage()); + System.err.println("❌ Error during refreshChatList: " + e.getMessage()); + e.printStackTrace(); } } + + public void createGroup() { System.out.print("Enter group ID: "); String groupId = scanner.nextLine(); @@ -174,7 +203,7 @@ public class ActionHandler { JSONObject req = new JSONObject(); req.put("action", "create_group"); req.put("user_id", Session.getUserUUID()); - req.put("group_id", groupId); // ID قابل نمایش + req.put("group_id", groupId); req.put("group_name", groupName); req.put("image_url", imageUrl.isBlank() ? JSONObject.NULL : imageUrl); @@ -201,7 +230,14 @@ public class ActionHandler { send(req); } - + public void addMember(UUID memberId){ + JSONObject req = new JSONObject(); + req.put("action", "add_member"); + req.put("user_id", Session.currentUser.getString("user_id")); + //req.put("group/channel",) + req.put("member_id", memberId.toString()); + send(req); + } @@ -209,21 +245,21 @@ public class ActionHandler { private void send(JSONObject request) { try { if (!request.has("action") || request.isNull("action")) { - System.err.println("Error: Request does not contain 'action'."); + System.err.println("❌ Invalid request: missing action."); return; } String action = request.getString("action"); this.out.println(request.toString()); - String responseText = this.in.readLine(); - if (responseText == null) { - System.out.println("No response from server."); + JSONObject response = TelegramClient.responseQueue.take(); + + if (response == null) { + System.out.println("⚠️ No response received."); return; } - JSONObject response = new JSONObject(responseText); - System.out.println("Server response: " + response.getString("message")); + System.out.println("✅ Server Response: " + response.getString("message")); String status = response.getString("status"); if (!"success".equals(status) || !response.has("data") || response.isNull("data")) @@ -233,26 +269,59 @@ public class ActionHandler { case "login": case "register": Session.currentUser = response.getJSONObject("data"); + JSONArray chatListJson = Session.currentUser.getJSONArray("chat_list"); List chatList = new ArrayList<>(); for (Object obj : chatListJson) { JSONObject chat = (JSONObject) obj; + ChatEntry entry = new ChatEntry( + UUID.fromString(chat.getString("internal_id")), chat.getString("id"), chat.getString("name"), chat.optString("image_url", ""), chat.getString("type"), - chat.isNull("last_message_time") ? null : - LocalDateTime.parse(chat.getString("last_message_time")) + chat.isNull("last_message_time") ? null : LocalDateTime.parse(chat.getString("last_message_time")), + chat.optBoolean("is_owner", false), + chat.optBoolean("is_admin", false) ); + + + chatList.add(entry); } + Session.chatList = chatList; break; - case "search": + case "searchInUsers": + JSONArray result = response.getJSONObject("data").getJSONArray("results"); + if (result.isEmpty()){ + System.out.println("No results found."); + } + else{ + System.out.println("Search Results:"); + for (int i = 0 ; i < result.length(); i++){ + JSONObject item = result.getJSONObject(i); + String type = item.getString("type"); + if(type.equals("user")){ + System.out.println((i + 1) + ". [" + type + "] " + + item.getString("name") + " (ID: " + item.getString("id") + ")"); + } + } + System.out.print("Select a result number to interact(0 for return): "); + int index = Integer.parseInt(scanner.nextLine()) - 1; + if (index < 0 || index >= result.length()) return; + JSONObject selected = result.getJSONObject(index); + String userId = selected.getString("id"); + String uuid = selected.getString("uuid"); + UUID memberId = UUID.fromString(uuid); + addMember(memberId); + } + break; + case "search" : JSONArray results = response.getJSONObject("data").getJSONArray("results"); if (results.isEmpty()) { @@ -262,6 +331,7 @@ public class ActionHandler { for (int i = 0; i < results.length(); i++) { JSONObject item = results.getJSONObject(i); String type = item.getString("type"); + if (type.equals("message")) { System.out.println((i + 1) + ". [message] \"" + item.getString("content") + "\"" + " (from: " + item.optString("sender", "N/A") + ", at: " + item.getString("time") + ")"); @@ -271,61 +341,92 @@ public class ActionHandler { } } - System.out.print("Select a result number to interact: "); + System.out.print("Select a result number to interact (or 0 to exit): "); int index = Integer.parseInt(scanner.nextLine()) - 1; - if (index < 0 || index >= results.length()) return; + if (index == -1) { + System.out.println("Exit..."); + return; + } + if (index < -1 || index >= results.length()) return; JSONObject selected = results.getJSONObject(index); String type = selected.getString("type"); switch (type) { case "user" -> { - String userId = selected.getString("id"); - String uuid = selected.getString("uuid"); + String uuidStr = selected.getString("uuid"); + UUID uuid = UUID.fromString(uuidStr); + + System.out.println("🔍 Looking for chat with internal_id='" + uuid + "' type='private'"); ChatEntry existing = Session.chatList.stream() - .filter(c -> c.getId().equals(userId) && c.getType().equals("private")) + .filter(c -> c.getId().equals(uuid) && c.getType().equals("private")) .findFirst() .orElse(null); if (existing != null) { openChat(existing); } else { - UUID contactId = UUID.fromString(uuid); - addContact(contactId); - ChatEntry newChat = fetchChatInfo(contactId.toString(), "private"); + System.out.println("ℹ Trying to add contact..."); + addContact(uuid); + refreshChatList(); - openChat(newChat); + System.out.println("🔄 Rechecking chat list..."); + + existing = Session.chatList.stream() + .filter(c -> c.getId().equals(uuid) && c.getType().equals("private")) + .findFirst() + .orElse(null); + + if (existing != null) { + openChat(existing); + } else { + System.out.println("❌ Failed to open chat. Try again later."); + } } } case "group", "channel" -> { - String id = selected.getString("id"); - String uuid = selected.getString("uuid"); + String uuidStr = selected.getString("uuid"); + UUID uuid = UUID.fromString(uuidStr); + + System.out.println("🔍 Looking for chat with internal_id='" + uuid + "' type='" + type + "'"); ChatEntry existing = Session.chatList.stream() - .filter(c -> c.getId().equals(id) && c.getType().equals(type)) + .filter(c -> c.getId().equals(uuid) && c.getType().equals(type)) .findFirst() .orElse(null); if (existing != null) { openChat(existing); } else { - joinGroupOrChannel(type, uuid); + System.out.println("ℹ Trying to join " + type + "..."); + joinGroupOrChannel(type, uuidStr); - ChatEntry newChat = fetchChatInfo(id, type); refreshChatList(); - openChat(newChat); + System.out.println("🔄 Rechecking chat list..."); + + existing = Session.chatList.stream() + .filter(c -> c.getId().equals(uuid) && c.getType().equals(type)) + .findFirst() + .orElse(null); + + if (existing != null) { + openChat(existing); + } else { + System.out.println("❌ Failed to open " + type + ". Try again later."); + } } } - case "message" -> { String receiverId = selected.getString("receiver_id"); String receiverType = selected.getString("receiver_type"); + System.out.println("🔍 Looking for chat with displayId='" + receiverId + "' type='" + receiverType + "'"); + ChatEntry chat = Session.chatList.stream() - .filter(c -> c.getId().equals(receiverId) && c.getType().equals(receiverType)) + .filter(c -> c.getDisplayId().equals(receiverId) && c.getType().equals(receiverType)) .findFirst() .orElseGet(() -> fetchChatInfo(receiverId, receiverType)); @@ -334,44 +435,68 @@ public class ActionHandler { default -> System.out.println("No interaction available for type: " + type); } - } - break; + + + + + break; case "create_group": case "create_channel": - if (status.equals("success") && response.has("data")) { - JSONObject chatJson = response.getJSONObject("data"); + if (response.has("data")) { + JSONObject chatJson = response.getJSONObject("data"); + ChatEntry chat = new ChatEntry( + UUID.fromString(chatJson.getString("internal_id")), chatJson.getString("id"), chatJson.getString("name"), chatJson.optString("image_url", ""), chatJson.getString("type"), - null + null, + chatJson.optBoolean("is_owner", false), + chatJson.optBoolean("is_admin", false) ); + + refreshChatList(); - System.out.println("✅ Created and opening chat..."); - openChat(chat); + System.out.println("✅ Created and opening chat..."); + openChat(chat); + } + + break; + + case "get_messages": + JSONArray messages = response.getJSONObject("data").getJSONArray("messages"); + 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 content = m.getString("content"); + String time = m.getString("send_at"); + + String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : "Other"; + System.out.println("[" + time + "] " + label + ": " + content); } + System.out.println("─────────────────────────────────────────────"); break; - - - case "get_messages": - // Optional: handle later + case "logout": + Session.currentUser = null; + Session.chatList = null; break; } - } catch (IOException e) { - System.err.println("Error communicating with server: " + e.getMessage()); } catch (Exception e) { - System.err.println("Client error: " + e.getMessage()); + System.err.println("❌ Error during send(): " + e.getMessage()); e.printStackTrace(); } } + public void userMenu(UUID internal_uuid) { while (true) { System.out.println("\nUser Menu:"); @@ -416,7 +541,11 @@ public class ActionHandler { System.out.print("Select a chat by number: "); int choice = Integer.parseInt(scanner.nextLine()) - 1; - if (choice < 0 || choice >= Session.chatList.size()) { + if(choice == -1){ + System.out.println("Exit..."); + return; + } + if (choice < -1 || choice >= Session.chatList.size()) { System.out.println("Invalid selection."); return; } @@ -426,18 +555,499 @@ public class ActionHandler { } private void openChat(ChatEntry chat) { - JSONObject request = new JSONObject(); - request.put("action", "get_messages"); - request.put("receiver_id", chat.getId()); - request.put("receiver_type", chat.getType()); - send(request); + JSONObject req = new JSONObject(); + req.put("action", "get_messages"); + req.put("receiver_id", chat.getId()); + req.put("receiver_type", chat.getType()); + send(req); + + boolean stayInChat = true; + + while (stayInChat) { + switch (chat.getType().trim().toLowerCase()) { + case "private" -> stayInChat = showPrivateChatMenu(chat); + case "group" -> stayInChat = showGroupChatMenu(chat); + case "channel" -> stayInChat = showChannelChatMenu(chat); + default -> { + System.out.println("❗ Unknown chat type: " + chat.getType()); + stayInChat = false; + } + } + } } + + + + + private boolean showPrivateChatMenu(ChatEntry chat) { + System.out.println("1. Send message"); + System.out.println("2. Block/Unblock"); + System.out.println("3. Delete chat (one-sided)"); + System.out.println("4. Delete chat (both sides)"); + System.out.println("5. Back"); + + + String input = scanner.nextLine(); + switch (input) { + case "1" -> sendMessageTo(chat.getId(), "private"); + case "2" -> toggleBlock(chat.getId()); + case "3" -> { + deleteChat(chat.getId(), false); + return false; + } + case "4" -> { + deleteChat(chat.getId(), true); + return false; + } + case "5" -> { + return false; + } + default -> System.out.println("Invalid choice."); + } + return true; + } + + private JSONObject getGroupPermissions(UUID groupId) { + JSONObject req = new JSONObject(); + req.put("action", "get_group_permissions"); + req.put("group_id", groupId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + return new JSONObject(); // پیش‌فرض خالی یعنی هیچ پرمیشنی + } + return res.getJSONObject("data"); + } + + + + private boolean showGroupChatMenu(ChatEntry chat) { + boolean isAdmin = chat.isAdmin(); + boolean isOwner = chat.isOwner(); + JSONObject perms = getGroupPermissions(chat.getId()); + + System.out.println("\n--- Group Chat Menu ---"); + System.out.println("1. Send Message"); + System.out.println("2. View Members"); + + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) { + System.out.println("3. Add Member"); + } + if (isOwner || (isAdmin && perms.optBoolean("can_edit_group", false))) { + System.out.println("4. Edit Group Info"); + } + if (isOwner || (isAdmin && perms.optBoolean("can_add_admins", false))) { + System.out.println("5. Add Admin"); + } + if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) { + System.out.println("6. Remove Admin"); + } + if (isOwner) { + System.out.println("7. Delete Group"); + } + System.out.println("8. Leave Group"); + System.out.println("0. Back to Chat List"); + + String input = scanner.nextLine(); + switch (input) { + case "1" -> sendMessageTo(chat.getId(), "group"); + case "2" -> viewGroupMembers(chat.getId()); + case "3" -> { + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) addMemberToGroup(chat.getId()); + else System.out.println("❌ You don't have permission."); + } + case "4" -> { + if (isOwner || (isAdmin && perms.optBoolean("can_edit_group", false))) editGroupInfo(chat.getId()); + else System.out.println("❌ You don't have permission."); + } + case "5" -> { + if (isOwner || (isAdmin && perms.optBoolean("can_add_admins", false))) addAdminToGroup(chat.getId()); + else System.out.println("❌ You don't have permission."); + } + case "6" -> { + if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) removeAdminFromGroup(chat.getId()); + else System.out.println("❌ You don't have permission."); + } + case "7" -> { + if (isOwner) deleteGroup(chat.getId()); + else System.out.println("❌ You don't have permission."); + return false; + } + case "8" -> { + leaveChat(chat.getId(), "group"); + return false; + } + case "0" -> { + return false; + } + default -> System.out.println("Invalid choice."); + } + return true; + } + + + + + private boolean showChannelChatMenu(ChatEntry chat) { + boolean isAdmin = chat.isAdmin(); + boolean isOwner = chat.isOwner(); + + System.out.println("\n--- Channel Menu ---"); + + if (isOwner || isAdmin) + System.out.println("1. Send Post"); + + System.out.println("2. View Subscribers"); + + if (isOwner || isAdmin) { + System.out.println("3. Add Admin"); + System.out.println("4. Edit Channel Info"); + } + + System.out.println("5. Unsubscribe"); + System.out.println("0. Back to Chat List"); + + String input = scanner.nextLine(); + switch (input) { + case "1" -> { + if (isOwner || isAdmin) sendMessageTo(chat.getId(), "channel"); + else System.out.println("❌ You are not allowed to post."); + } + case "2" -> viewChannelSubscribers(chat.getId()); + case "3" -> { + if (isOwner) addAdminToChannel(chat.getId()); + else System.out.println("❌ Only owner can add admins."); + } + case "4" -> { + if (isOwner || isAdmin) editChannelInfo(chat.getId()); + else System.out.println("❌ You don't have permission."); + } + case "5" -> { + leaveChat(chat.getId(), "channel"); + return false; // خروج کامل از چت + } + case "0" -> { + return false; // بازگشت به لیست چت‌ها + } + default -> System.out.println("Invalid choice."); + } + return true; // همچنان در منو باقی بمان + } + + + + + + + + + + private void addAdminToGroup(UUID groupId) { + JSONObject req = new JSONObject(); + req.put("action", "view_group_members"); + req.put("group_id", groupId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch members."); + return; + } + + JSONArray members = res.getJSONObject("data").getJSONArray("members"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Members List ---"); + for (int i = 0; i < members.length(); i++) { + JSONObject m = members.getJSONObject(i); + if (m.getString("role").equals("member")) { + eligible.add(m); + System.out.println((eligible.size()) + ". " + m.getString("profile_name") + " (" + m.getString("user_id") + ")"); + } + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No eligible members."); + return; + } + + System.out.print("Select a member to promote: "); + int choice = Integer.parseInt(scanner.nextLine()) - 1; + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("internal_uuid"); // دقت کن internal_uuid + + JSONObject permissions = new JSONObject(); + System.out.print("Can add members? (true/false): "); + permissions.put("can_add_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove members? (true/false): "); + permissions.put("can_remove_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add admins? (true/false): "); + permissions.put("can_add_admins", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove admins? (true/false): "); + permissions.put("can_remove_admins", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can edit group info? (true/false): "); + permissions.put("can_edit_group", Boolean.parseBoolean(scanner.nextLine())); + + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "add_admin_to_group"); + promoteReq.put("group_id", groupId.toString()); + promoteReq.put("user_id", targetInternalUUID); // ارسال internal_uuid واقعی + promoteReq.put("permissions", permissions); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes != null) + System.out.println(promoteRes.getString("message")); + } + + + private void viewGroupMembers(UUID groupId) { + JSONObject req = new JSONObject(); + req.put("action", "view_group_members"); + req.put("group_id", groupId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + if (res.getString("status").equals("success")) { + JSONArray members = res.getJSONObject("data").getJSONArray("members"); + + System.out.println("\n--- Group Members ---"); + for (int i = 0; i < members.length(); i++) { + JSONObject m = members.getJSONObject(i); + String name = m.getString("profile_name"); + String userId = m.getString("user_id"); + String role = m.getString("role"); + System.out.printf("- %s (ID: %s) [%s]\n", name, userId, role); + } + } else { + System.out.println("❌ Failed to fetch members."); + } + } + + + + private void removeAdminFromGroup(UUID groupId) { + System.out.print("Enter user_id to remove from admin: "); + String userId = scanner.nextLine().trim(); + + JSONObject req = new JSONObject(); + req.put("action", "remove_admin_from_group"); + req.put("group_id", groupId.toString()); + req.put("user_id", userId); + + JSONObject res = sendWithResponse(req); + if (res != null) + System.out.println(res.getString("message")); + } + + + private void deleteGroup(UUID groupId) { + System.out.print("Are you sure you want to delete the group? (yes/no): "); + String confirm = scanner.nextLine().trim().toLowerCase(); + + if (!confirm.equals("yes")) { + System.out.println("❌ Delete cancelled."); + return; + } + + JSONObject req = new JSONObject(); + req.put("action", "delete_group"); + req.put("group_id", groupId.toString()); + + JSONObject res = sendWithResponse(req); + if (res != null && res.getString("status").equals("success")) { + System.out.println("✅ Group deleted successfully."); + refreshChatList(); + } else { + System.out.println("❌ Failed to delete group."); + } + } + + 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("both", both); + send(req); + + JSONObject resp = sendWithResponse(req); + if (resp.getString("status").equals("success")) { + System.out.println("✅ Chat deleted successfully."); + } else { + System.out.println("⚠️ Failed to delete chat."); + } + } + + + + private void toggleBlock(UUID userId) { + JSONObject req = new JSONObject(); + req.put("action", "toggle_block"); + req.put("user_id", Session.getUserUUID()); + req.put("target_id", userId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + String status = res.getString("status"); + String message = res.getString("message"); + + if (status.equals("success")) { + System.out.println("🔒 " + message); + } else { + System.out.println("❌ " + message); + } + } + + + private void leaveChat(UUID id, String type) { + JSONObject req = new JSONObject(); + req.put("action", "leave_chat"); + req.put("user_id", Session.getUserUUID()); + req.put("chat_id", id.toString()); + req.put("chat_type", type); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + if (res.getBoolean("success")) { + System.out.println("✅ You left the chat."); + } else { + System.out.println("❌ " + res.getString("message")); + } + } + + 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) { + System.out.print("Enter user_id to add: "); + String userId = scanner.nextLine().trim(); + + JSONObject req = new JSONObject(); + req.put("action", "add_member_to_group"); + req.put("group_id", groupId.toString()); + req.put("user_id", userId); + + JSONObject res = sendWithResponse(req); + if (res != null) + System.out.println(res.getString("message")); + } + + private void editGroupInfo(UUID groupId) { + System.out.print("Enter new group name: "); + String newName = scanner.nextLine().trim(); + + System.out.print("Enter new group description: "); + String newDesc = scanner.nextLine().trim(); + + JSONObject req = new JSONObject(); + req.put("action", "edit_group_info"); + req.put("group_id", groupId.toString()); + req.put("name", newName); + req.put("description", newDesc); + + JSONObject res = sendWithResponse(req); + if (res != null) + System.out.println(res.getString("message")); + } + + + private void viewChannelSubscribers(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_subscribers"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + if (res.getBoolean("success")) { + JSONArray subs = res.getJSONArray("subscribers"); + System.out.println("--- Subscribers ---"); + for (int i = 0; i < subs.length(); i++) { + JSONObject s = subs.getJSONObject(i); + System.out.println("- " + s.getString("profile_name")); + } + } else { + System.out.println("❌ Failed to load subscribers."); + } + } + + private void addAdminToChannel(UUID channelId) { + System.out.print("Enter user_id to promote: "); + String userId = scanner.nextLine().trim(); + + JSONObject req = new JSONObject(); + req.put("action", "add_admin_to_channel"); + req.put("channel_id", channelId.toString()); + req.put("user_id", userId); + + JSONObject res = sendWithResponse(req); + if (res != null) + System.out.println(res.getString("message")); + } + + private void editChannelInfo(UUID channelId) { + System.out.print("Enter new channel name: "); + String name = scanner.nextLine().trim(); + + System.out.print("Enter new description: "); + String desc = scanner.nextLine().trim(); + + JSONObject req = new JSONObject(); + req.put("action", "edit_channel_info"); + req.put("channel_id", channelId.toString()); + req.put("name", name); + req.put("description", desc); + + JSONObject res = sendWithResponse(req); + if (res != null) + System.out.println(res.getString("message")); + } + + + private JSONObject getResponse() { + try { + return TelegramClient.responseQueue.take(); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to get server response"); + } + } + + public void logout() { if (Session.currentUser != null && Session.currentUser.has("user_id")) { JSONObject request = new JSONObject(); request.put("action", "logout"); - request.put("user_id", Session.currentUser.getString("internalUUID")); + request.put("user_id", Session.currentUser.getString("internal_uuid")); send(request); Session.currentUser = null; @@ -449,4 +1059,150 @@ public class ActionHandler { + public void processIncomingEvents() { + try { + while (in.ready()) { + String line = in.readLine(); + if (line == null) continue; + + JSONObject response = new JSONObject(line); + if (!response.has("action")) continue; + + String action = response.getString("action"); + + switch (action) { + case "new_message" -> { + JSONObject msg = response.getJSONObject("data"); + 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.print(">> "); + } + + case "user_status_changed" -> { + JSONObject msg = response.getJSONObject("data"); + System.out.println("\n🔄 User Status Changed:"); + System.out.println("User: " + msg.getString("user_id")); + System.out.println("Status: " + msg.getString("status")); + System.out.print(">> "); + } + + case "update_group_or_channel" -> { + JSONObject data = response.getJSONObject("data"); + System.out.println("\n📢 " + data.getString("chat_type") + " updated: " + data.getString("new_name")); + System.out.print(">> "); + } + + + default -> { + if (!action.equals("search")) { + System.out.println("\n❓ Unknown action received: " + action); + System.out.print(">> "); + } + } + } + } + } catch (Exception e) { + System.out.println("🔴 Failed to process event: " + e.getMessage()); + } + } + + + public void addAdminToEntity(String type, UUID entityId) { + System.out.print("Enter user ID to promote to admin: "); + String targetUserId = scanner.nextLine().trim(); + + JSONObject permissions = new JSONObject(); + System.out.print("Can send messages? (true/false): "); + permissions.put("can_send", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can edit info? (true/false): "); + permissions.put("can_edit", Boolean.parseBoolean(scanner.nextLine())); + + JSONObject req = new JSONObject(); + req.put("action", type.equals("group") ? "add_admin_to_group" : "add_admin_to_channel"); + req.put(type + "_id", entityId.toString()); + req.put("target_user_id", targetUserId); + req.put("permissions", permissions); + + send(req); + JSONObject res = getResponse(); + System.out.println(res.getString("message")); + } + + + public void editAdminPermissions(String type, UUID entityId) { + System.out.print("Enter user ID of the admin to edit: "); + String targetUserId = scanner.nextLine().trim(); + + JSONObject permissions = new JSONObject(); + System.out.print("Can send messages? (true/false): "); + permissions.put("can_send", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can edit info? (true/false): "); + permissions.put("can_edit", Boolean.parseBoolean(scanner.nextLine())); + + JSONObject req = new JSONObject(); + req.put("action", type.equals("group") ? "edit_group_admin_permissions" : "edit_channel_admin_permissions"); + req.put(type + "_id", entityId.toString()); + req.put("target_user_id", targetUserId); + req.put("permissions", permissions); + + send(req); + JSONObject res = getResponse(); + System.out.println(res.getString("message")); + } + + + public void viewAdmins(String type, UUID entityId) { + JSONObject req = new JSONObject(); + req.put("action", type.equals("group") ? "view_group_admins" : "view_channel_admins"); + req.put(type + "_id", entityId.toString()); + + send(req); + JSONObject res = getResponse(); + if (res.getString("status").equals("success")) { + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + System.out.println("📋 Admins in this " + type + ":"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + System.out.printf("- %s (%s)\n", admin.getString("name"), admin.getString("role")); + } + } else { + System.out.println("❌ " + res.getString("message")); + } + } + + + + + + + private JSONObject sendWithResponse(JSONObject request) { + try { + if (!request.has("action") || request.isNull("action")) { + System.err.println("❌ Invalid request: missing action."); + return null; + } + + String action = request.getString("action"); + this.out.println(request.toString()); + + JSONObject response = TelegramClient.responseQueue.take(); + + if (response == null) { + System.out.println("⚠️ No response received."); + return null; + } + + System.out.println("✅ Server Response: " + response.getString("message")); + return response; + + } catch (Exception e) { + System.err.println("❌ Error during sendWithResponse(): " + e.getMessage()); + e.printStackTrace(); + return null; + } + } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java b/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java new file mode 100644 index 0000000..ff50959 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Client/EventProcessorThread.java @@ -0,0 +1,20 @@ +package org.to.telegramfinalproject.Client; + +public class EventProcessorThread extends Thread { + private final ActionHandler handler; + + public EventProcessorThread(ActionHandler handler) { + this.handler = handler; + setDaemon(true); + } + + @Override + public void run() { + while (true) { + try { + Thread.sleep(2000); + handler.processIncomingEvents(); + } catch (InterruptedException ignored) {} + } + } +} diff --git a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java index 4de32f5..30ca3a0 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -15,72 +15,107 @@ public class IncomingMessageListener implements Runnable { @Override public void run() { try { + System.out.println("👂 Real-Time Listener started."); + String line; while ((line = in.readLine()) != null) { JSONObject response = new JSONObject(line); + System.out.println("📥 Received raw line: " + line); - if (!response.has("action")) continue; - String action = response.getString("action"); - - switch (action) { - case "new_message" -> { - JSONObject msg = response.getJSONObject("data"); - 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.print(">> "); + if (response.has("action")) { + String action = response.getString("action"); + if (isRealTimeEvent(action)) { + handleRealTimeEvent(response); + } else { + TelegramClient.responseQueue.put(response); } - - case "message_edited" -> { - JSONObject msg = response.getJSONObject("data"); - System.out.println("\n✏️ Message Edited:"); - System.out.println("ID: " + msg.getString("message_id")); - System.out.println("New Content: " + msg.getString("new_content")); - System.out.println("Edit Time: " + msg.getString("edit_time")); - System.out.print(">> "); - } - - case "message_deleted" -> { - JSONObject msg = response.getJSONObject("data"); - System.out.println("\n🗑️ Message Deleted:"); - System.out.println("Message ID: " + msg.getString("message_id")); - System.out.print(">> "); - } - - case "status_change" -> { - JSONObject msg = response.getJSONObject("data"); - System.out.println("\n🔄 User Status Changed:"); - System.out.println("User: " + msg.getString("user_id")); - System.out.println("Status: " + msg.getString("status")); - System.out.print(">> "); - } - - case "system_notification" -> { - JSONObject msg = response.getJSONObject("data"); - System.out.println("\n⚠️ System Notification:"); - System.out.println(msg.getString("content")); - System.out.print(">> "); - } - - case "contact_added" ->{ - System.out.println("\n🔔 You were added by a new contact: " + response.getString("user_id")); - System.out.print(">> "); - } - - - - default -> { - if (!action.equals("search")) { // ignore action: search - System.out.println("\n❓ Unknown action received: " + action); - System.out.print(">> "); - } - } - + } else if (response.has("status") && response.has("message")) { + TelegramClient.responseQueue.put(response); + } else { + TelegramClient.responseQueue.put(response); } } + } catch (Exception e) { System.out.println("🔴 Listener stopped: " + e.getMessage()); } } + + private boolean isRealTimeEvent(String action) { + return switch (action) { + case "new_message", "message_edited", "message_deleted", + "user_status_changed", "added_to_group", "added_to_channel", + "update_group_or_channel", "chat_deleted", + "blocked_by_user", "unblocked_by_user", "message_seen" -> true; + default -> false; + }; + } + + private void handleRealTimeEvent(JSONObject response) { + String action = response.getString("action"); + JSONObject msg = response.getJSONObject("data"); + + 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")); + } + + case "message_edited" -> { + System.out.println("\n✏️ Message Edited:"); + System.out.println("ID: " + msg.getString("message_id")); + System.out.println("New Content: " + msg.getString("new_content")); + System.out.println("Edit Time: " + msg.getString("edited_at")); + } + + case "message_deleted" -> { + System.out.println("\n🗑️ Message Deleted:"); + System.out.println("Message ID: " + msg.getString("message_id")); + } + + case "user_status_changed" -> { + System.out.println("\n🔄 User Status Changed:"); + System.out.println("User: " + msg.getString("user_id")); + System.out.println("Status: " + msg.getString("status")); + } + + case "added_to_group" -> { + System.out.println("\n👥 You were added to a group: " + msg.getString("chat_name")); + } + + case "added_to_channel" -> { + System.out.println("\n📢 You were added to a channel: " + msg.getString("chat_name")); + } + + case "update_group_or_channel" -> { + System.out.println("\n🔄 Group/Channel updated: " + msg.getString("new_name")); + } + + case "chat_deleted" -> { + System.out.println("\n🗑️ Chat deleted: " + msg.getString("chat_id")); + } + + case "blocked_by_user" -> { + System.out.println("\n⛔ You were blocked by user: " + msg.getString("blocker_id")); + } + + case "unblocked_by_user" -> { + System.out.println("\n✅ You were unblocked by user: " + msg.getString("unblocker_id")); + } + + case "message_seen" -> { + System.out.println("\n👁️ Your message was seen:"); + System.out.println("Message ID: " + msg.getString("message_id")); + System.out.println("Seen at: " + msg.getString("seen_at")); + } + + default -> { + System.out.println("\n❓ Unknown real-time action: " + action); + } + } + + System.out.print(">> "); + } } diff --git a/src/main/java/org/to/telegramfinalproject/Client/RealTimeBuffer.java b/src/main/java/org/to/telegramfinalproject/Client/RealTimeBuffer.java new file mode 100644 index 0000000..0b66a4c --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Client/RealTimeBuffer.java @@ -0,0 +1,12 @@ +package org.to.telegramfinalproject.Client; + + +import org.json.JSONObject; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class RealTimeBuffer { + public static final BlockingQueue incomingEvents = new LinkedBlockingQueue<>(); +} + diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index ba614c0..16ae524 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -9,6 +9,8 @@ import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; import java.util.UUID; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; public class TelegramClient { private static final String SERVER_HOST = "localhost"; @@ -18,6 +20,7 @@ public class TelegramClient { private PrintWriter out; private final Scanner scanner; ActionHandler handler = null; + public static BlockingQueue responseQueue = new LinkedBlockingQueue<>(); public TelegramClient() { this.scanner = new Scanner(System.in); @@ -28,76 +31,52 @@ public class TelegramClient { this.socket = new Socket(SERVER_HOST, SERVER_PORT); this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); this.out = new PrintWriter(this.socket.getOutputStream(), true); - System.out.println(" Connected to Telegram Server"); + System.out.println("✅ Connected to Telegram Server"); this.handler = new ActionHandler(this.out, this.in, this.scanner); - this.showMainMenu(); - } catch (IOException e) { - System.err.println("Error connecting to server: " + e.getMessage()); - } + // 👂 فقط این ترد مجاز به خواندن از in است + Thread listenerThread = new Thread(new IncomingMessageListener(in)); + listenerThread.setDaemon(true); + listenerThread.start(); + + showMainMenu(); + + } catch (IOException e) { + System.err.println("❌ Error connecting to server: " + e.getMessage()); + } } private void showMainMenu() { - while(true) { + while (true) { System.out.println("Main Menu:"); System.out.println("1. Register"); System.out.println("2. Login"); System.out.println("3. Exit"); System.out.print("Choose an option: "); - switch (this.scanner.nextLine()) { - case "1": - this.handler.register(); - break; - case "2": - this.handler.loginHandler(); + String choice = scanner.nextLine(); + + switch (choice) { + case "1" -> handler.register(); + case "2" -> { + handler.loginHandler(); if (Session.currentUser != null) { - System.out.println("Login successful."); - UUID internalId = UUID.fromString(Session.currentUser.getString("internalUUID")); - //Thread listenerThread = new Thread(new IncomingMessageListener(in)); - //listenerThread.setDaemon(true); - //listenerThread.start(); - this.handler.userMenu(internalId); - + System.out.println("✅ Login successful."); + UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid")); + handler.userMenu(internalId); } else { - System.out.println("Login failed."); + System.out.println("❌ Login failed."); } - break; - case "3": - System.out.println("Disconnecting..."); - - if (Session.currentUser != null && Session.currentUser.has("internalUUID")) { - try { - JSONObject logoutRequest = new JSONObject(); - logoutRequest.put("action", "logout"); - logoutRequest.put("user_id", Session.currentUser.getString("internalUUID")); - out.println(logoutRequest.toString()); - in.readLine(); - } catch (Exception e) { - System.err.println("Failed to notify server on logout: " + e.getMessage()); - } - } - - try { - if (socket != null) socket.close(); - if (in != null) in.close(); - if (out != null) out.close(); - System.out.println("Disconnected."); - } catch (IOException e) { - System.err.println("Error closing connection: " + e.getMessage()); - } - + } + case "3" -> { + System.out.println("Exiting..."); return; - - - default: - System.out.println("Invalid choice. Please try again."); + } + default -> System.out.println("Invalid choice."); } } } public static void main(String[] args) { - TelegramClient client = new TelegramClient(); - client.start(); + new TelegramClient().start(); } } - diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index 3e9783b..01ac6b4 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -1,5 +1,6 @@ package org.to.telegramfinalproject.Database; +import org.json.JSONObject; import org.to.telegramfinalproject.Models.Channel; import org.to.telegramfinalproject.Models.Group; @@ -81,7 +82,7 @@ public class ChannelDatabase { public static List getSubscriberUUIDs(UUID channelInternalUUID) { List subscriberIds = new ArrayList<>(); - String sql = "SELECT user_id FROM channel_subscribe WHERE channel_id = ?"; + String sql = "SELECT user_id FROM channel_subscribers WHERE channel_id = ?"; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { @@ -128,7 +129,7 @@ public class ChannelDatabase { public static boolean isUserSubscribed(UUID userId, UUID channelInternalId) { - String sql = "SELECT * FROM channel_subscribe WHERE user_id = ? AND channel_id = ?"; + String sql = "SELECT * FROM channel_subscribers WHERE user_id = ? AND channel_id = ?"; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setObject(1, userId); stmt.setObject(2, channelInternalId); @@ -240,12 +241,14 @@ public class ChannelDatabase { } } - public static void addSubscriber(UUID channelId, UUID userId) { - String sql = "INSERT INTO channel_subscribers (channel_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; + public static void addSubscriber(UUID channelId, UUID userId, String role) { + String sql = "INSERT INTO channel_subscribers (channel_id, user_id, role) VALUES (?, ?, ?) ON CONFLICT DO NOTHING"; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setObject(1, channelId); stmt.setObject(2, userId); + stmt.setString(3, role); + stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); @@ -279,4 +282,142 @@ public class ChannelDatabase { } + public static boolean addOwnerToChannel(UUID channelId, UUID userId) { + String sql = "INSERT INTO channel_subscribers (channel_id, user_id, role) VALUES (?, ?, 'owner')"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean addAdminToChannel(UUID channelId, UUID userId, JSONObject permissions) { + String sql = "UPDATE channel_subscribers SET role = 'admin', permissions = ?::jsonb WHERE channel_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, permissions.toString()); + stmt.setObject(2, channelId); + stmt.setObject(3, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static String getChannelRole(UUID channelId, UUID userId) { + String sql = "SELECT role FROM channel_subscribers WHERE channel_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return rs.getString("role"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return "subscriber"; // پیش‌فرض + } + + + + public static JSONObject getChannelPermissions(UUID channelId, UUID userId) { + String sql = "SELECT permissions FROM channel_subscribers WHERE channel_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return new JSONObject(rs.getString("permissions")); + } + } catch (Exception e) { + e.printStackTrace(); + } + return new JSONObject(); + } + + + + public static boolean updateChannelAdminPermissions(UUID channelId, UUID userId, JSONObject permissions) { + String sql = "UPDATE channel_subscribers SET permissions = ?::jsonb WHERE channel_id = ? AND user_id = ? AND role = 'admin'"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, permissions.toString()); + stmt.setObject(2, channelId); + stmt.setObject(3, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + + public static List getChannelAdminsAndOwner(UUID channelId) { + String sql = "SELECT user_id, role, permissions FROM channel_subscribers WHERE channel_id = ? AND role IN ('owner', 'admin')"; + List admins = new ArrayList<>(); + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + JSONObject obj = new JSONObject(); + obj.put("user_id", rs.getObject("user_id").toString()); + obj.put("role", rs.getString("role")); + obj.put("permissions", new JSONObject(rs.getString("permissions"))); + admins.add(obj); + } + } catch (Exception e) { + e.printStackTrace(); + } + return admins; + } + + + public static boolean isOwner(UUID channelId, UUID userId) { + String sql = "SELECT role FROM channel_subscribers WHERE channel_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return "owner".equals(rs.getString("role")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + public static boolean isAdmin(UUID channelId, UUID userId) { + String sql = "SELECT role FROM channel_subscribers WHERE channel_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + String role = rs.getString("role"); + return "admin".equals(role) || "owner".equals(role); // owner هم admin هست + } + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java index a890995..220f551 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java @@ -49,19 +49,47 @@ public class ContactDatabase { } } - public boolean blockContact(UUID user_id, UUID contact_id) { - String sql = "UPDATE contacts SET is_blocked = TRUE WHERE user_id = ? AND contact_id = ?"; - try (Connection connection = getConnection()) { - PreparedStatement stmt = connection.prepareStatement(sql); - stmt.setObject(1, user_id); - stmt.setObject(2, contact_id); - return stmt.executeUpdate() > 0; + public static boolean toggleBlock(UUID userId, UUID targetId) { + String selectSql = "SELECT is_blocked FROM contacts WHERE user_id = ? AND contact_id = ?"; + String updateSql = "UPDATE contacts SET is_blocked = ? WHERE user_id = ? AND contact_id = ?"; + + try (Connection conn = getConnection(); + PreparedStatement selectStmt = conn.prepareStatement(selectSql)) { + + selectStmt.setObject(1, userId); + selectStmt.setObject(2, targetId); + + ResultSet rs = selectStmt.executeQuery(); + if (rs.next()) { + boolean currentlyBlocked = rs.getBoolean("is_blocked"); + + try (PreparedStatement updateStmt = conn.prepareStatement(updateSql)) { + updateStmt.setBoolean(1, !currentlyBlocked); + updateStmt.setObject(2, userId); + updateStmt.setObject(3, targetId); + updateStmt.executeUpdate(); + } + return !currentlyBlocked; + } else { + // اگر رابطه وجود نداره، اول باید کاربر رو به contact ها اضافه کنیم + String insertSql = "INSERT INTO contacts (user_id, contact_id, is_blocked) VALUES (?, ?, ?)"; + try (PreparedStatement insertStmt = conn.prepareStatement(insertSql)) { + insertStmt.setObject(1, userId); + insertStmt.setObject(2, targetId); + insertStmt.setBoolean(3, true); + insertStmt.executeUpdate(); + } + return true; + } + } catch (SQLException e) { e.printStackTrace(); - return false; } + return false; } + + public boolean unblockContact(UUID user_id, UUID contact_id) { String sql = "UPDATE contacts SET is_blocked = FALSE WHERE user_id = ? AND contact_id = ?"; try (Connection connection = getConnection()) { @@ -160,6 +188,76 @@ public class ContactDatabase { } + public static boolean deleteChatOneSide(UUID currentUserId, UUID otherUserId) { + 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 (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?) + """; + + try (Connection conn = getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, currentUserId); + stmt.setObject(2, currentUserId); + stmt.setObject(3, currentUserId); + stmt.setObject(4, otherUserId); + stmt.setObject(5, otherUserId); + stmt.setObject(6, currentUserId); + + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static boolean deleteChatBoth(UUID currentUserId, UUID otherUserId) { + String sqlDeleteMessages = """ + DELETE FROM messages + WHERE receiver_type = 'private' AND ( + (sender_id = ? AND receiver_id = ?) OR + (sender_id = ? AND receiver_id = ?) + ) + """; + + String sqlDeleteChat = """ + DELETE FROM private_chat + WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?) + """; + + try (Connection conn = getConnection()) { + conn.setAutoCommit(false); + + try (PreparedStatement stmtMsg = conn.prepareStatement(sqlDeleteMessages); + PreparedStatement stmtChat = conn.prepareStatement(sqlDeleteChat)) { + + stmtMsg.setObject(1, currentUserId); + stmtMsg.setObject(2, otherUserId); + stmtMsg.setObject(3, otherUserId); + stmtMsg.setObject(4, currentUserId); + stmtMsg.executeUpdate(); + + stmtChat.setObject(1, currentUserId); + stmtChat.setObject(2, otherUserId); + stmtChat.setObject(3, otherUserId); + stmtChat.setObject(4, currentUserId); + stmtChat.executeUpdate(); + + conn.commit(); + return true; + + } catch (SQLException e) { + conn.rollback(); + e.printStackTrace(); + return false; + } + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 1cbc98d..d8313a4 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -1,5 +1,7 @@ package org.to.telegramfinalproject.Database; +import org.json.JSONArray; +import org.json.JSONObject; import org.to.telegramfinalproject.Models.Group; import java.sql.Connection; @@ -284,4 +286,223 @@ public class GroupDatabase { } + public static boolean addOwnerToGroup(UUID groupId, UUID userId) { + String sql = "INSERT INTO group_members (group_id, user_id, role) VALUES (?, ?, 'owner')"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean addAdminToGroup(UUID groupId, UUID userId, JSONObject permissions) { + String sql = """ + UPDATE group_members + SET role = 'admin', + permissions = ?::jsonb + WHERE group_id = ? AND user_id = ? AND role = 'member' + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setString(1, permissions.toString()); + stmt.setObject(2, groupId); + stmt.setObject(3, userId); + + return stmt.executeUpdate() > 0; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + + public static String getGroupRole(UUID groupId, UUID userId) { + String sql = "SELECT role FROM group_members WHERE group_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return rs.getString("role"); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return "member"; // پیش‌فرض + } + + + + + + public static boolean updateGroupAdminPermissions(UUID groupId, UUID userId, JSONObject permissions) { + String sql = """ + UPDATE group_members + SET permissions = ?::jsonb + WHERE group_id = ? AND user_id = ? AND role = 'admin' + """; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, permissions.toString()); + stmt.setObject(2, groupId); + stmt.setObject(3, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static List getGroupAdminsAndOwner(UUID groupId) { + String sql = "SELECT user_id, role, permissions FROM group_members WHERE group_id = ? AND role IN ('owner', 'admin')"; + List admins = new ArrayList<>(); + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + JSONObject obj = new JSONObject(); + obj.put("user_id", rs.getObject("user_id").toString()); + obj.put("role", rs.getString("role")); + obj.put("permissions", new JSONObject(rs.getString("permissions"))); + admins.add(obj); + } + } catch (Exception e) { + e.printStackTrace(); + } + return admins; + } + + + public static boolean isOwner(UUID groupId, UUID userId) { + String sql = "SELECT 1 FROM group_members WHERE group_id = ? AND user_id = ? AND role = 'owner'"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + + ResultSet rs = stmt.executeQuery(); + return rs.next(); // اگر رکوردی پیدا شد یعنی owner است + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + public static boolean isAdmin(UUID groupId, UUID userId) { + String sql = "SELECT role FROM group_members WHERE group_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + String role = rs.getString("role"); + return "admin".equals(role) || "owner".equals(role); // owner هم admin هست + } + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + public static JSONObject getGroupPermissions(UUID groupId, UUID userId) { + String sql = "SELECT permissions FROM group_members WHERE group_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + String permissions = rs.getString("permissions"); + if (permissions != null && !permissions.isBlank()) { + return new JSONObject(permissions); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return new JSONObject(); + } + + + + public static JSONArray getGroupMembers(UUID groupId) { + String sql = """ + SELECT u.profile_name, u.user_id, u.internal_uuid, gm.role, gm.permissions + FROM group_members gm + JOIN users u ON gm.user_id = u.internal_uuid + WHERE gm.group_id = ? + """; + + JSONArray members = new JSONArray(); + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, groupId); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + JSONObject member = new JSONObject(); + member.put("profile_name", rs.getString("profile_name")); + member.put("user_id", rs.getString("user_id")); // آیدی قابل نمایش + member.put("internal_uuid", rs.getObject("internal_uuid").toString()); + member.put("role", rs.getString("role")); + + String permissions = rs.getString("permissions"); + if (permissions != null && !permissions.isBlank()) { + member.put("permissions", new JSONObject(permissions)); + } + + members.put(member); + } + + return members; + + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } + + + public static boolean demoteAdminToMember(UUID groupId, UUID userId) { + String sql = "UPDATE group_members SET role = 'member', permissions = '{}'::jsonb WHERE group_id = ? AND user_id = ? AND role = 'admin'"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static boolean removeMemberFromGroup(UUID groupId, UUID userId) { + String sql = "DELETE FROM group_members WHERE group_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); + stmt.setObject(2, userId); + return stmt.executeUpdate() > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 957b61d..22ef33b 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -217,6 +217,80 @@ public class MessageDatabase { return result; } + public static List privateChatHistory(UUID user1, UUID user2) { + 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 = ?) + ) + 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); + + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + result.add(extractMessage(rs)); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return result; + } + + + + 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 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 searchMessagesInGroups(List groupIds, String keyword) { List result = new ArrayList<>(); diff --git a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java index e5f09b1..a330da4 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java @@ -21,7 +21,7 @@ public class userDatabase { try { User var6; - try (Connection conn = this.getConnection()) { + try (Connection conn = ConnectionDb.connect()) { try (PreparedStatement stmt = conn.prepareStatement(query)) { stmt.setString(1, userId); ResultSet rs = stmt.executeQuery(); diff --git a/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java b/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java index 52d3916..cbd0940 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java +++ b/src/main/java/org/to/telegramfinalproject/Models/ChatEntry.java @@ -1,28 +1,70 @@ package org.to.telegramfinalproject.Models; +import org.json.JSONObject; + import java.time.LocalDateTime; +import java.util.UUID; public class ChatEntry { - private final String name; - private final String id; - private final String imageUrl; - private final String type; // "private", "group", "channel" - private final LocalDateTime lastMessageTime; + private UUID internalId; + private String displayId; + private String name; + private String imageUrl; + private String type; + private LocalDateTime lastMessageTime; - public ChatEntry(String name, String id, String imageUrl, String type, LocalDateTime lastMessageTime) { + // 🔹 نقش‌ها + private boolean isOwner = false; + private boolean isAdmin = false; + private JSONObject permissions; + + + public ChatEntry(UUID internalId, String displayId, String name, String imageUrl, String type, LocalDateTime lastMessageTime) { + this.internalId = internalId; + this.displayId = displayId; this.name = name; - this.id = id; this.imageUrl = imageUrl; this.type = type; this.lastMessageTime = lastMessageTime; } - public String getName() { - return name; + // ✅ کانستراکتور اضافه‌شده برای پشتیبانی از نقش‌ها (اختیاری، برای استفاده‌های جدید) + public ChatEntry(UUID internalId, String displayId, String name, String imageUrl, String type, LocalDateTime lastMessageTime, boolean isOwner, boolean isAdmin) { + this(internalId, displayId, name, imageUrl, type, lastMessageTime); + this.isOwner = isOwner; + this.isAdmin = isAdmin; + this.permissions = permissions; + } - public String getId() { - return id; + // 🟩 گتر و ستر جدید + public boolean isOwner() { + return isOwner; + } + + public void setOwner(boolean owner) { + isOwner = owner; + } + + public boolean isAdmin() { + return isAdmin; + } + + public void setAdmin(boolean admin) { + isAdmin = admin; + } + + // سایر گترها + public UUID getId() { + return internalId; + } + + public String getDisplayId() { + return displayId; + } + + public String getName() { + return name; } public String getImageUrl() { @@ -36,4 +78,13 @@ public class ChatEntry { public LocalDateTime getLastMessageTime() { return lastMessageTime; } + + + public JSONObject getPermissions() { + return permissions; + } + + public void setPermissions(JSONObject permissions) { + this.permissions = permissions; + } } diff --git a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java index eba775f..a9ed199 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java +++ b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java @@ -28,7 +28,7 @@ public class JsonUtil { public static JSONObject userToJson(User user) { JSONObject obj = new JSONObject(); - obj.put("internalUUID", user.getInternal_uuid().toString()); + obj.put("internal_uuid", user.getInternal_uuid().toString()); obj.put("user_id", user.getUser_id() != null ?user.getUser_id().toString() :JSONObject.NULL); obj.put("username", user.getUsername()); obj.put("profile_name", user.getProfile_name()); @@ -154,11 +154,15 @@ public class JsonUtil { for (ChatEntry entry : chatList) { JSONObject obj = new JSONObject(); - obj.put("id", entry.getId() != null ?entry.getId() :JSONObject.NULL); + obj.put("internal_id", entry.getId().toString()); + obj.put("id", entry.getDisplayId()); obj.put("name", entry.getName()); - obj.put("image_url", entry.getImageUrl() != null ?entry.getImageUrl() :JSONObject.NULL); + obj.put("image_url", entry.getImageUrl()); obj.put("type", entry.getType()); - obj.put("last_message_time", entry.getLastMessageTime() != null ? entry.getLastMessageTime().toString() : JSONObject.NULL); + obj.put("last_message_time", entry.getLastMessageTime() == null ? JSONObject.NULL : entry.getLastMessageTime().toString()); + obj.put("is_owner", entry.isOwner()); + obj.put("is_admin", entry.isAdmin()); + jsonArray.put(obj); } diff --git a/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java b/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java index 9334717..372600b 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java +++ b/src/main/java/org/to/telegramfinalproject/Models/PrivateChat.java @@ -4,7 +4,7 @@ import java.time.LocalDateTime; import java.util.UUID; public class PrivateChat { - private UUID chat_id; + private final UUID chat_id; private UUID user1_id; private UUID user2_id; private LocalDateTime created_at; diff --git a/src/main/java/org/to/telegramfinalproject/Server/ChannelService.java b/src/main/java/org/to/telegramfinalproject/Server/ChannelService.java index a8f7a33..7901d62 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ChannelService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ChannelService.java @@ -14,7 +14,7 @@ public class ChannelService { boolean inserted = ChannelDatabase.insertChannel(internalUUID, channelId, channelName, creatorUUID, imageUrl, now); if (inserted) { - ChannelDatabase.addSubscriber(internalUUID, creatorUUID); // اضافه کردن سازنده + ChannelDatabase.addSubscriber(internalUUID, creatorUUID,"owner"); return true; } return false; diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 8fe3943..6bee449 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -4,6 +4,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Database.*; import org.to.telegramfinalproject.Models.*; +import org.to.telegramfinalproject.Utils.GroupPermissionUtil; import java.io.*; import java.net.Socket; @@ -92,17 +93,54 @@ public class ClientHandler implements Runnable { 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.getUser_id(), target.getProfile_name(), target.getImage_url(), "private", last)); + + 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 + )); } + for (Group group : groups) { LocalDateTime last = MessageDatabase.getLastMessageTime(group.getInternal_uuid(), "group"); - chatList.add(new ChatEntry(group.getGroup_id(), group.getGroup_name(), group.getImage_url(), "group", last)); + boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), user.getInternal_uuid()); + boolean isAdmin = GroupDatabase.isAdmin(group.getInternal_uuid(), user.getInternal_uuid()); + + chatList.add(new ChatEntry( + group.getInternal_uuid(), + group.getGroup_id(), + group.getGroup_name(), + group.getImage_url(), + "group", + last, + isOwner, + isAdmin + )); } + for (Channel channel : channels) { LocalDateTime last = MessageDatabase.getLastMessageTime(channel.getInternal_uuid(), "channel"); - chatList.add(new ChatEntry(channel.getChannel_id(), channel.getChannel_name(), channel.getImage_url(), "channel", last)); + boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), user.getInternal_uuid()); + boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), user.getInternal_uuid()); + + chatList.add(new ChatEntry( + channel.getInternal_uuid(), + channel.getChannel_id(), + channel.getChannel_name(), + channel.getImage_url(), + "channel", + last, + isOwner, + isAdmin + )); } + chatList.sort((a, b) -> { if (a.getLastMessageTime() == null) return 1; if (b.getLastMessageTime() == null) return -1; @@ -115,21 +153,46 @@ public class ClientHandler implements Runnable { } break; } - case "logout": { String user_Id = requestJson.optString("user_id"); - if (userId != null && !user_Id.isEmpty()) { - UUID uuid = UUID.fromString(user_Id); - userDatabase.updateUserStatus(uuid, "offline"); - userDatabase.updateLastSeen(uuid); - SessionManager.removeUser(uuid); - response = new ResponseModel("success", "Logged out."); + if (user_Id != null && !user_Id.isEmpty()) { + try { + UUID uuid = UUID.fromString(user_Id); + userDatabase.updateUserStatus(uuid, "offline"); + userDatabase.updateLastSeen(uuid); + SessionManager.removeUser(uuid); + response = new ResponseModel("success", "Logged out."); + } catch (IllegalArgumentException e) { + response = new ResponseModel("error", "Invalid UUID format."); + } } else { response = new ResponseModel("error", "Invalid user_id for logout."); } break; } + + case "searchInUsers":{ + String keyword = requestJson.optString("keyword"); + List results = new ArrayList<>(); + String user_Id = requestJson.getString("user_id"); + User currentUser = new userDatabase().findByUserId(user_Id); + UUID currentUserUUID = currentUser.getInternal_uuid(); + + for (User u : new userDatabase().searchUsers(keyword, currentUserUUID)) { + JSONObject obj = new JSONObject(); + obj.put("type", "user"); + obj.put("id", u.getUser_id()); + obj.put("uuid", u.getInternal_uuid().toString()); + obj.put("name", u.getProfile_name()); + results.add(obj); + } + JSONObject data = new JSONObject(); + data.put("results", new JSONArray(results)); + response = new ResponseModel("success", "Search results found", data); + break; + } + case "search": { String keyword = requestJson.optString("keyword"); List results = new ArrayList<>(); @@ -228,7 +291,7 @@ public class ClientHandler implements Runnable { } case "join_group": { - UUID userUUID = UUID.fromString(requestJson.getString("user_id")); // مستقیم internalUUID دریافت می‌کنیم + UUID userUUID = UUID.fromString(requestJson.getString("user_id")); Group group = GroupDatabase.findByInternalUUID(UUID.fromString(requestJson.getString("id"))); if (group == null) { response = new ResponseModel("error", "Group not found."); @@ -244,7 +307,7 @@ public class ClientHandler implements Runnable { } case "join_channel": { - UUID userUUID = UUID.fromString(requestJson.getString("user_id")); // مستقیم internalUUID دریافت می‌کنیم + UUID userUUID = UUID.fromString(requestJson.getString("user_id")); Channel channel = ChannelDatabase.findByInternalUUID(UUID.fromString(requestJson.getString("id"))); if (channel == null) { response = new ResponseModel("error", "Channel not found."); @@ -261,57 +324,88 @@ public class ClientHandler implements Runnable { case "get_chat_info": { - String id = requestJson.getString("receiver_id"); - String type = requestJson.getString("receiver_type"); - JSONObject data = new JSONObject(); + try { + String id = requestJson.getString("receiver_id"); + String type = requestJson.getString("receiver_type"); + JSONObject data = new JSONObject(); - switch (type) { - case "private" -> { - User u = new userDatabase().findByUserId(id); - if (u != null) { - data.put("name", u.getProfile_name()); - data.put("image_url", u.getImage_url()); - } else { - response = new ResponseModel("error", "User not found."); - break; + switch (type) { + case "private" -> { + userDatabase userDatabase = new userDatabase(); + User u = userDatabase.findByUserId(id); + if (u == null) { + try { + UUID uuid = UUID.fromString(id); + u = userDatabase.findByInternalUUID(uuid); + } catch (IllegalArgumentException ignored) {} + } + + if (u != null) { + data.put("internal_id", u.getInternal_uuid().toString()); + data.put("name", u.getProfile_name()); + data.put("image_url", u.getImage_url()); + data.put("type", "private"); + data.put("id", u.getUser_id()); + } else { + response = new ResponseModel("error", "User not found."); + break; + } } - } - case "group" -> { - Group g = GroupDatabase.findByGroupId(id); - if (g != null) { - data.put("name", g.getGroup_name()); - data.put("image_url", g.getImage_url()); - } else { - response = new ResponseModel("error", "Group not found."); - break; + case "group" -> { + Group group = GroupDatabase.findByInternalUUID(UUID.fromString(id)); + if (group != null) { + data.put("internal_id", group.getInternal_uuid().toString()); + data.put("name", group.getGroup_name()); + data.put("image_url", group.getImage_url()); + data.put("type", "group"); + data.put("id", group.getGroup_id()); + + // اضافه کردن owner و admin بودن + boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), currentUser.getInternal_uuid()); + boolean isAdmin = GroupDatabase.isAdmin(group.getInternal_uuid(), currentUser.getInternal_uuid()); + + data.put("is_owner", isOwner); + data.put("is_admin", isAdmin); + } else { + response = new ResponseModel("error", "Group not found."); + break; + } } - } - case "channel" -> { - Channel c = ChannelDatabase.findByChannelId(id); - if (c != null) { - data.put("name", c.getChannel_name()); - data.put("image_url", c.getImage_url()); - } else { - response = new ResponseModel("error", "Channel not found."); + + + case "channel" -> { + Channel channel = ChannelDatabase.findByChannelId(id); + if (channel != null) { + data.put("internal_id", channel.getInternal_uuid().toString()); + data.put("name", channel.getChannel_name()); + data.put("image_url", channel.getImage_url()); + data.put("type", "channel"); + data.put("id", channel.getChannel_id()); + } else { + response = new ResponseModel("error", "Channel not found."); + break; + } + } + + default -> { + response = new ResponseModel("error", "Unknown type."); break; } } - default -> { - response = new ResponseModel("error", "Unknown type."); - break; + if (response == null) { + response = new ResponseModel("success", "Chat info fetched", data); } + + } catch (Exception e) { + response = new ResponseModel("error", "Error fetching chat info: " + e.getMessage()); } - - if (data.has("name")) { - response = new ResponseModel("success", "Chat info fetched", data); - } - break; } + case "get_chat_list": { String userIdStr = requestJson.getString("user_id"); User user = new userDatabase().findByUserId(userIdStr); @@ -321,25 +415,58 @@ public class ClientHandler implements Runnable { List channels = ChannelDatabase.getChannelsByUser(user.getInternal_uuid()); List chatList = new ArrayList<>(); - 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.getUser_id(), target.getProfile_name(), target.getImage_url(), "private", last)); + 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 + )); } for (Group group : groups) { LocalDateTime last = MessageDatabase.getLastMessageTime(group.getInternal_uuid(), "group"); - chatList.add(new ChatEntry(group.getGroup_id(), group.getGroup_name(), group.getImage_url(), "group", last)); + boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), user.getInternal_uuid()); + boolean isAdmin = GroupDatabase.isAdmin(group.getInternal_uuid(), user.getInternal_uuid()); + + chatList.add(new ChatEntry( + group.getInternal_uuid(), + group.getGroup_id(), + group.getGroup_name(), + group.getImage_url(), + "group", + last, + isOwner, + isAdmin + )); } for (Channel channel : channels) { LocalDateTime last = MessageDatabase.getLastMessageTime(channel.getInternal_uuid(), "channel"); - chatList.add(new ChatEntry(channel.getChannel_id(), channel.getChannel_name(), channel.getImage_url(), "channel", last)); + boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), user.getInternal_uuid()); + boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), user.getInternal_uuid()); + + chatList.add(new ChatEntry( + channel.getInternal_uuid(), + channel.getChannel_id(), + channel.getChannel_name(), + channel.getImage_url(), + "channel", + last, + isOwner, + isAdmin + )); } + chatList.sort((a, b) -> { if (a.getLastMessageTime() == null) return 1; if (b.getLastMessageTime() == null) return -1; @@ -354,7 +481,7 @@ public class ClientHandler implements Runnable { } case "create_group": { try { - String groupId = requestJson.getString("group_id"); // ID نمایشی + String groupId = requestJson.getString("group_id"); String groupName = requestJson.getString("group_name"); String userIdStr = requestJson.getString("user_id"); String imageUrl = requestJson.optString("image_url", null); @@ -363,16 +490,30 @@ public class ClientHandler implements Runnable { boolean created = GroupService.createGroup(groupId, groupName, creatorUUID, imageUrl); - response = created - ? new ResponseModel("success", "Group created.") - : new ResponseModel("error", "Group creation failed."); + if (created) { + Group createdGroup = GroupDatabase.findByGroupId(groupId); + if (createdGroup != null) { + JSONObject data = new JSONObject(); + data.put("internal_id", createdGroup.getInternal_uuid().toString()); + data.put("id", createdGroup.getGroup_id()); + data.put("name", createdGroup.getGroup_name()); + data.put("image_url", createdGroup.getImage_url()); + data.put("type", "group"); + response = new ResponseModel("success", "Group created.", data); + } else { + response = new ResponseModel("error", "Group created but not found."); + } + } else { + response = new ResponseModel("error", "Group creation failed."); + } } catch (Exception e) { response = new ResponseModel("error", "Error creating group: " + e.getMessage()); } break; } + case "create_channel": { try { String channelId = requestJson.getString("channel_id"); @@ -384,9 +525,23 @@ public class ClientHandler implements Runnable { boolean created = ChannelService.createChannel(channelId, channelName, creatorUUID, imageUrl); - response = created - ? new ResponseModel("success", "Channel created.") - : new ResponseModel("error", "Channel creation failed."); + if (created) { + Channel createdChannel = ChannelDatabase.findByChannelId(channelId); + if (createdChannel != null) { + JSONObject data = new JSONObject(); + data.put("internal_id", createdChannel.getInternal_uuid().toString()); + data.put("id", createdChannel.getChannel_id()); + data.put("name", createdChannel.getChannel_name()); + data.put("image_url", createdChannel.getImage_url()); + data.put("type", "channel"); + + response = new ResponseModel("success", "Channel created.", data); + } else { + response = new ResponseModel("error", "Channel created but not found."); + } + } else { + response = new ResponseModel("error", "Channel creation failed."); + } } catch (Exception e) { response = new ResponseModel("error", "Error creating channel: " + e.getMessage()); @@ -395,6 +550,319 @@ public class ClientHandler implements Runnable { } + case "add_admin_to_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); + JSONObject permissions = requestJson.optJSONObject("permissions"); + + //if (!ChannelPermissionUtil.canAddAdmins(channelId, currentUser.getInternal_uuid())) { + //response = new ResponseModel("error", "You are not allowed to add admins to the channel."); + //break; + //} + + + boolean success = ChannelDatabase.addAdminToChannel(channelId, targetUserId, permissions); + response = success + ? new ResponseModel("success", "Admin added to channel.") + : new ResponseModel("error", "Failed to add admin."); + break; + } + + + + case "edit_channel_admin_permissions": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); + JSONObject permissions = requestJson.optJSONObject("permissions"); + + String role = ChannelDatabase.getChannelRole(channelId, currentUser.getInternal_uuid()); + if (!role.equals("owner")) { + response = new ResponseModel("error", "Only owner can update admin permissions."); + break; + } + + boolean success = ChannelDatabase.updateChannelAdminPermissions(channelId, targetUserId, permissions); + response = success + ? new ResponseModel("success", "Permissions updated.") + : new ResponseModel("error", "Failed to update permissions."); + break; + } + + + case "view_channel_admins": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + + //if (!ChannelPermissionUtil.canAddAdmins(channelId, currentUser.getInternal_uuid())) { + // response = new ResponseModel("error", "You are not allowed to add admins to the channel."); + //break; + //} + + + List admins = ChannelDatabase.getChannelAdminsAndOwner(channelId); + JSONObject data = new JSONObject(); + data.put("admins", new JSONArray(admins)); + response = new ResponseModel("success", "Admins fetched.", data); + break; + } + + + + case "add_admin_to_group": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); + JSONObject permissions = requestJson.optJSONObject("permissions"); + + if (!GroupPermissionUtil.canAddAdmins(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to add admins."); + break; + } + + + boolean success = GroupDatabase.addAdminToGroup(groupId, targetUserId, permissions); + response = success + ? new ResponseModel("success", "Admin added to group.") + : new ResponseModel("error", "Failed to add admin."); + break; + } + + + case "edit_group_admin_permissions": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); + JSONObject permissions = requestJson.optJSONObject("permissions"); + + if (!GroupPermissionUtil.canAddAdmins(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to edit admin permissions."); + break; + } + + boolean success = GroupDatabase.updateGroupAdminPermissions(groupId, targetUserId, permissions); + response = success + ? new ResponseModel("success", "Permissions updated.") + : new ResponseModel("error", "Failed to update permissions."); + break; + } + + + case "remove_admin_from_group": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + String targetUserIdStr = requestJson.getString("user_id"); + + // تبدیل user_id نمایشی به internal_uuid واقعی + User targetUser = new userDatabase().findByUserId(targetUserIdStr); + if (targetUser == null) { + response = new ResponseModel("error", "User not found."); + break; + } + + UUID targetUserUUID = targetUser.getInternal_uuid(); + + if (!GroupPermissionUtil.canRemoveAdmins(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to remove admins."); + break; + } + + String targetRole = GroupDatabase.getGroupRole(groupId, targetUserUUID); + if (targetRole.equals("owner")) { + response = new ResponseModel("error", "You cannot remove the owner."); + break; + } + if (!targetRole.equals("admin")) { + response = new ResponseModel("error", "Target user is not an admin."); + break; + } + + boolean success = GroupDatabase.demoteAdminToMember(groupId, targetUserUUID); + response = success + ? new ResponseModel("success", "Admin removed successfully.") + : new ResponseModel("error", "Failed to remove admin."); + break; + } + + + + case "add_member_to_group": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); + + if (!GroupPermissionUtil.canAddMembers(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to add members."); + break; + } + + if (GroupDatabase.isUserInGroup(targetUserId, groupId)) { + response = new ResponseModel("error", "User is already a member."); + break; + } + + boolean success = GroupDatabase.addMemberToGroup(targetUserId, groupId); + response = success + ? new ResponseModel("success", "Member added to group.") + : new ResponseModel("error", "Failed to add member."); + break; + } + + + case "remove_member_from_group": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); + + if (!GroupPermissionUtil.canRemoveMembers(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to remove members."); + break; + } + + String targetRole = GroupDatabase.getGroupRole(groupId, targetUserId); + if (targetRole.equals("owner") || targetRole.equals("admin")) { + response = new ResponseModel("error", "You cannot remove admins or owner this way."); + break; + } + + boolean success = GroupDatabase.removeMemberFromGroup(groupId, targetUserId); + response = success + ? new ResponseModel("success", "Member removed from group.") + : new ResponseModel("error", "Failed to remove member."); + break; + } + + + case "view_group_admins": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + + String role = GroupDatabase.getGroupRole(groupId, currentUser.getInternal_uuid()); + if (!role.equals("owner") && !role.equals("admin")) { + response = new ResponseModel("error", "You are not authorized to view admins."); + break; + } + + List admins = GroupDatabase.getGroupAdminsAndOwner(groupId); + JSONObject data = new JSONObject(); + data.put("admins", new JSONArray(admins)); + response = new ResponseModel("success", "Admins fetched.", data); + break; + } + + + case "get_messages": { + try { + String receiverId = requestJson.getString("receiver_id"); + String receiverType = requestJson.getString("receiver_type"); + + List messages = new ArrayList<>(); + + switch (receiverType) { + case "private" -> { + // تبدیل user_id به internal_uuid + User otherUser = new userDatabase().findByInternalUUID(UUID.fromString(receiverId)); + if (otherUser == null) { + response = new ResponseModel("error", "User not found."); + break; + } + messages = MessageDatabase.privateChatHistory(currentUser.getInternal_uuid(), otherUser.getInternal_uuid()); + } + case "group" -> { + Group group = GroupDatabase.findByInternalUUID(UUID.fromString(receiverId)); + if (group == null) { + response = new ResponseModel("error", "Group not found."); + break; + } + messages = MessageDatabase.groupChatHistory(group.getInternal_uuid()); + } + + + case "channel" -> { + Channel channel = ChannelDatabase.findByChannelId(receiverId); + if (channel == null) { + response = new ResponseModel("error", "Channel not found."); + break; + } + messages = MessageDatabase.channelChatHistory(channel.getInternal_uuid()); + } + default -> { + response = new ResponseModel("error", "Invalid receiver type."); + break; + } + } + + if (response == null) { + JSONArray messageArray = new JSONArray(); + for (Message m : messages) { + JSONObject obj = new JSONObject(); + obj.put("id", m.getMessage_id().toString()); + obj.put("sender_id", m.getSender_id().toString()); + obj.put("receiver_id", m.getReceiver_id().toString()); + obj.put("receiver_type", m.getReceiver_type()); + obj.put("content", m.getContent()); + obj.put("send_at", m.getSend_at().toString()); + messageArray.put(obj); + } + + JSONObject data = new JSONObject(); + data.put("messages", messageArray); + + response = new ResponseModel("success", "Messages fetched.", data); + } + + } catch (Exception e) { + response = new ResponseModel("error", "Error fetching messages: " + e.getMessage()); + } + break; + } + + case "toggle_block": { + try { + UUID userUUID = UUID.fromString(requestJson.getString("user_id")); + UUID targetUUID = UUID.fromString(requestJson.getString("target_id")); + + boolean isBlocked = ContactDatabase.toggleBlock(userUUID, targetUUID); + + String message = isBlocked ? "🔒 User blocked successfully." : "🔓 User unblocked successfully."; + response = new ResponseModel("success", message); + + } catch (Exception e) { + response = new ResponseModel("error", "Error processing block/unblock: " + e.getMessage()); + } + break; + } + + + + case "view_group_members" : { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + + JSONArray members = GroupDatabase.getGroupMembers(groupId); + + if (members != null) { + JSONObject data = new JSONObject(); + data.put("members", members); + response = new ResponseModel("success", "Members fetched successfully.", data); + } else { + response = new ResponseModel("error", "Failed to fetch members."); + } + break; + } + + + + case "delete_private_chat" : { + UUID targetId = UUID.fromString(requestJson.getString("target_id")); + boolean both = requestJson.getBoolean("both"); + response = PrivateChatService.deletePrivateChat(currentUser.getInternal_uuid(), targetId, both); + break; + } + + + case "get_group_permissions": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + userId = currentUser.getInternal_uuid(); + + JSONObject permissions = GroupDatabase.getGroupPermissions(groupId, userId); + + response = new ResponseModel("success", "Permissions fetched.", permissions); + break; + } + + diff --git a/src/main/java/org/to/telegramfinalproject/Server/GroupService.java b/src/main/java/org/to/telegramfinalproject/Server/GroupService.java index 31da28b..1d79728 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/GroupService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/GroupService.java @@ -14,10 +14,13 @@ public class GroupService { boolean inserted = GroupDatabase.insertGroup(internalUUID, groupId, groupName, creatorUUID, imageUrl, now); if (inserted) { - GroupDatabase.addMember(internalUUID, creatorUUID, "owner"); // سازنده owner می‌شود + GroupDatabase.addMember(internalUUID, creatorUUID, "owner"); return true; } return false; } + + + } diff --git a/src/main/java/org/to/telegramfinalproject/Server/PrivateChatService.java b/src/main/java/org/to/telegramfinalproject/Server/PrivateChatService.java new file mode 100644 index 0000000..190da7c --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Server/PrivateChatService.java @@ -0,0 +1,22 @@ +package org.to.telegramfinalproject.Server; + + +import org.to.telegramfinalproject.Database.ContactDatabase; +import org.to.telegramfinalproject.Models.ResponseModel; + +import java.util.UUID; + +public class PrivateChatService { + + public static ResponseModel deletePrivateChat(UUID currentUserId, UUID targetUserId, boolean both) { + boolean success = both ? + ContactDatabase.deleteChatBoth(currentUserId, targetUserId) : + ContactDatabase.deleteChatOneSide(currentUserId, targetUserId); + + if (success) { + return new ResponseModel("success", "Chat deleted successfully"); + } else { + return new ResponseModel("error", "Chat not found or failed to delete"); + } + } +} diff --git a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java index d2a341f..68e89ba 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java +++ b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java @@ -24,6 +24,8 @@ public class RealTimeEventDispatcher { PrintWriter out = new PrintWriter(socket.getOutputStream(), true); + System.out.println("🚀 Sending to user: " + userId + " → " + data); + out.println(data.toString()); } catch (IOException e) { System.err.println("❌ Error sending to user: " + e.getMessage()); diff --git a/src/main/java/org/to/telegramfinalproject/Utils/GroupPermissionUtil.java b/src/main/java/org/to/telegramfinalproject/Utils/GroupPermissionUtil.java new file mode 100644 index 0000000..8bcb3f5 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Utils/GroupPermissionUtil.java @@ -0,0 +1,59 @@ +package org.to.telegramfinalproject.Utils; + +import org.json.JSONObject; +import org.to.telegramfinalproject.Database.GroupDatabase; + +import java.util.UUID; + +public class GroupPermissionUtil { + + public static boolean canAddMembers(UUID groupId, UUID userId) { + String role = GroupDatabase.getGroupRole(groupId, userId); + if (role.equals("owner")) return true; + if (role.equals("admin")) { + JSONObject permissions = GroupDatabase.getGroupPermissions(groupId, userId); + return permissions.optBoolean("can_add_members", false); + } + return false; + } + + public static boolean canRemoveMembers(UUID groupId, UUID userId) { + String role = GroupDatabase.getGroupRole(groupId, userId); + if (role.equals("owner")) return true; + if (role.equals("admin")) { + JSONObject permissions = GroupDatabase.getGroupPermissions(groupId, userId); + return permissions.optBoolean("can_remove_members", false); + } + return false; + } + + public static boolean canAddAdmins(UUID groupId, UUID userId) { + String role = GroupDatabase.getGroupRole(groupId, userId); + if (role.equals("owner")) return true; + if (role.equals("admin")) { + JSONObject permissions = GroupDatabase.getGroupPermissions(groupId, userId); + return permissions.optBoolean("can_add_admins", false); + } + return false; + } + + public static boolean canRemoveAdmins(UUID groupId, UUID userId) { + String role = GroupDatabase.getGroupRole(groupId, userId); + if (role.equals("owner")) return true; + if (role.equals("admin")) { + JSONObject permissions = GroupDatabase.getGroupPermissions(groupId, userId); + return permissions.optBoolean("can_remove_admins", false); + } + return false; + } + + public static boolean canEditGroup(UUID groupId, UUID userId) { + String role = GroupDatabase.getGroupRole(groupId, userId); + if (role.equals("owner")) return true; + if (role.equals("admin")) { + JSONObject permissions = GroupDatabase.getGroupPermissions(groupId, userId); + return permissions.optBoolean("can_edit_group", false); + } + return false; + } +} From d3f449b8d24c9387fbc16d48de4eb85b38ad07a9 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 1 Jul 2025 15:31:12 +0330 Subject: [PATCH 5/8] add member and remove member in a group --- .../Client/ActionHandler.java | 168 ++++++++++++++++-- .../Database/ChannelDatabase.java | 16 ++ .../Server/ClientHandler.java | 54 +++++- 3 files changed, 221 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index ade0bfb..ba18656 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -10,6 +10,8 @@ import java.io.PrintWriter; import java.time.LocalDateTime; import java.util.*; +import static org.to.telegramfinalproject.Database.ChannelDatabase.addSubscriberToChannel; + public class ActionHandler { private final PrintWriter out; private final BufferedReader in; @@ -88,6 +90,67 @@ public class ActionHandler { } + + public void searchEligibleUsers(String entityType, UUID entityId) { + System.out.print("Enter keyword to search: "); + String keyword = scanner.nextLine().trim(); + + if (Session.currentUser == null || !Session.currentUser.has("user_id")) { + System.out.println("You must be logged in to search."); + return; + } + + JSONObject req = new JSONObject(); + req.put("action", "searchEligibleUsers"); + req.put("keyword", keyword); + req.put("user_id", Session.currentUser.getString("user_id")); + req.put("entity_id", entityId.toString()); + req.put("entity_type", entityType); // group یا channel + + JSONObject res = sendWithResponse(req); + + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ " + (res != null ? res.getString("message") : "No response received.")); + return; + } + + JSONArray results = res.getJSONObject("data").getJSONArray("results"); + if (results.isEmpty()) { + System.out.println("⚠️ No eligible users found."); + return; + } + + System.out.println("\nEligible Users:"); + for (int i = 0; i < results.length(); i++) { + JSONObject user = results.getJSONObject(i); + System.out.println((i + 1) + ". " + user.getString("name") + " (ID: " + user.getString("id") + ")"); + } + + System.out.print("Select a user to add (or 0 to cancel): "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= results.length()) { + System.out.println("Cancelled."); + return; + } + + JSONObject selected = results.getJSONObject(choice); + UUID targetUUID = UUID.fromString(selected.getString("uuid")); + + if (entityType.equals("group")) { + addMemberToGroup(entityId, targetUUID); + } else if (entityType.equals("channel")) { + addSubscriberToChannel(entityId, targetUUID); + } + } + + private void addContact(UUID contactId) { JSONObject req = new JSONObject(); req.put("action", "add_contact"); @@ -554,6 +617,8 @@ public class ActionHandler { openChat(selected); } + + private void openChat(ChatEntry chat) { JSONObject req = new JSONObject(); req.put("action", "get_messages"); @@ -615,7 +680,7 @@ public class ActionHandler { JSONObject res = sendWithResponse(req); if (res == null || !res.getString("status").equals("success")) { - return new JSONObject(); // پیش‌فرض خالی یعنی هیچ پرمیشنی + return new JSONObject(); } return res.getJSONObject("data"); } @@ -643,10 +708,13 @@ public class ActionHandler { if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) { System.out.println("6. Remove Admin"); } - if (isOwner) { - System.out.println("7. Delete Group"); + if (isOwner || (isAdmin && perms.optBoolean("can_remove_members", false))) { + System.out.println("7. Remove member"); } - System.out.println("8. Leave Group"); + if (isOwner) { + System.out.println("8. Delete Group"); + } + System.out.println("9. Leave Group"); System.out.println("0. Back to Chat List"); String input = scanner.nextLine(); @@ -654,7 +722,8 @@ public class ActionHandler { case "1" -> sendMessageTo(chat.getId(), "group"); case "2" -> viewGroupMembers(chat.getId()); case "3" -> { - if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) addMemberToGroup(chat.getId()); + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) + searchEligibleUsers("group", chat.getId()); else System.out.println("❌ You don't have permission."); } case "4" -> { @@ -669,12 +738,15 @@ public class ActionHandler { if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) removeAdminFromGroup(chat.getId()); else System.out.println("❌ You don't have permission."); } - case "7" -> { + case "7" ->{ + if(isOwner || (isAdmin && perms.optBoolean("can_remove_members",false))) removeMemberFromGroup(chat.getId()); + } + case "8" -> { if (isOwner) deleteGroup(chat.getId()); else System.out.println("❌ You don't have permission."); return false; } - case "8" -> { + case "9" -> { leaveChat(chat.getId(), "group"); return false; } @@ -739,6 +811,65 @@ public class ActionHandler { + private void removeMemberFromGroup(UUID groupId) { + JSONObject req = new JSONObject(); + req.put("action", "view_group_members"); + req.put("group_id", groupId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch members."); + return; + } + + JSONArray members = res.getJSONObject("data").getJSONArray("members"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Members List ---"); + for (int i = 0; i < members.length(); i++) { + JSONObject m = members.getJSONObject(i); + String role = m.getString("role"); + String profileName = m.getString("profile_name"); + String userId = m.getString("user_id"); + + // فقط اونر غیرقابل حذف + if (role.equals("owner")) continue; + + eligible.add(m); + System.out.println((eligible.size()) + ". " + profileName + " (" + userId + ") [" + role + "]"); + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No removable members."); + return; + } + + System.out.print("Select a member to remove: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("internal_uuid"); + + JSONObject removeReq = new JSONObject(); + removeReq.put("action", "remove_member_from_group"); + removeReq.put("group_id", groupId.toString()); + removeReq.put("user_id", targetInternalUUID); + + JSONObject removeRes = sendWithResponse(removeReq); + if (removeRes != null) + System.out.println(removeRes.getString("message")); + } @@ -948,20 +1079,31 @@ public class ActionHandler { } } - private void addMemberToGroup(UUID groupId) { - System.out.print("Enter user_id to add: "); - String userId = scanner.nextLine().trim(); - + private void addMemberToGroup(UUID groupId, UUID userId) { JSONObject req = new JSONObject(); req.put("action", "add_member_to_group"); req.put("group_id", groupId.toString()); - req.put("user_id", userId); + req.put("user_id", userId.toString()); JSONObject res = sendWithResponse(req); - if (res != null) + if (res != null) { System.out.println(res.getString("message")); + } } + private void addSubscriberToChannel(UUID channelId, UUID userId) { + JSONObject req = new JSONObject(); + req.put("action", "add_subscriber_to_channel"); + req.put("channel_id", channelId.toString()); + req.put("user_id", userId.toString()); + + JSONObject res = sendWithResponse(req); + if (res != null) { + System.out.println(res.getString("message")); + } + } + + private void editGroupInfo(UUID groupId) { System.out.print("Enter new group name: "); String newName = scanner.nextLine().trim(); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index 01ac6b4..f293efa 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -419,5 +419,21 @@ public class ChannelDatabase { } + public static boolean isUserInChannel(UUID userId, UUID channelId) { + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement( + "SELECT 1 FROM channel_subscribers WHERE channel_id = ? AND user_id = ?")) { + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + + ResultSet rs = stmt.executeQuery(); + return rs.next(); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 6bee449..7eb2de9 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -193,6 +193,41 @@ public class ClientHandler implements Runnable { break; } + + + case "searchEligibleUsers": { + String keyword = requestJson.optString("keyword"); + UUID entityId = UUID.fromString(requestJson.getString("entity_id")); + String entityType = requestJson.getString("entity_type"); // group یا channel + + String user_Id = requestJson.getString("user_id"); + User currentUser = new userDatabase().findByUserId(user_Id); + + List results = new ArrayList<>(); + for (User u : new userDatabase().searchUsers(keyword, currentUser.getInternal_uuid())) { + + boolean isMember = switch (entityType) { + case "group" -> GroupDatabase.isUserInGroup(u.getInternal_uuid(), entityId); + case "channel" -> ChannelDatabase.isUserInChannel(u.getInternal_uuid(), entityId); + default -> true; + }; + + if (isMember || u.getInternal_uuid().equals(currentUser.getInternal_uuid())) continue; + + JSONObject obj = new JSONObject(); + obj.put("id", u.getUser_id()); + obj.put("uuid", u.getInternal_uuid().toString()); + obj.put("name", u.getProfile_name()); + results.add(obj); + } + + + JSONObject data = new JSONObject(); + data.put("results", new JSONArray(results)); + response = new ResponseModel("success", "Eligible users found", data); + break; + } + case "search": { String keyword = requestJson.optString("keyword"); List results = new ArrayList<>(); @@ -479,6 +514,9 @@ public class ClientHandler implements Runnable { response = new ResponseModel("success", "Chat list updated.", data); break; } + + + case "create_group": { try { String groupId = requestJson.getString("group_id"); @@ -644,11 +682,14 @@ public class ClientHandler implements Runnable { } + + + case "remove_admin_from_group": { UUID groupId = UUID.fromString(requestJson.getString("group_id")); String targetUserIdStr = requestJson.getString("user_id"); - // تبدیل user_id نمایشی به internal_uuid واقعی + User targetUser = new userDatabase().findByUserId(targetUserIdStr); if (targetUser == null) { response = new ResponseModel("error", "User not found."); @@ -713,8 +754,13 @@ public class ClientHandler implements Runnable { } String targetRole = GroupDatabase.getGroupRole(groupId, targetUserId); - if (targetRole.equals("owner") || targetRole.equals("admin")) { - response = new ResponseModel("error", "You cannot remove admins or owner this way."); + if (targetRole == null) { + response = new ResponseModel("error", "User is not a member of the group."); + break; + } + + if (targetRole.equals("owner")) { + response = new ResponseModel("error", "You cannot remove the owner."); break; } @@ -726,6 +772,7 @@ public class ClientHandler implements Runnable { } + case "view_group_admins": { UUID groupId = UUID.fromString(requestJson.getString("group_id")); @@ -752,7 +799,6 @@ public class ClientHandler implements Runnable { switch (receiverType) { case "private" -> { - // تبدیل user_id به internal_uuid User otherUser = new userDatabase().findByInternalUUID(UUID.fromString(receiverId)); if (otherUser == null) { response = new ResponseModel("error", "User not found."); From 0fbccc2c4ca871f76d4aff0419fa63de7e6f6279 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 1 Jul 2025 16:06:29 +0330 Subject: [PATCH 6/8] Edit group info --- .../Client/ActionHandler.java | 87 +++++++++++++++---- .../Database/GroupDatabase.java | 47 +++++++++- .../Server/ClientHandler.java | 41 ++++++++- 3 files changed, 155 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index ba18656..7980f72 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -797,14 +797,14 @@ public class ActionHandler { } case "5" -> { leaveChat(chat.getId(), "channel"); - return false; // خروج کامل از چت + return false; } case "0" -> { - return false; // بازگشت به لیست چت‌ها + return false; } default -> System.out.println("Invalid choice."); } - return true; // همچنان در منو باقی بمان + return true; } @@ -1105,24 +1105,81 @@ public class ActionHandler { private void editGroupInfo(UUID groupId) { - System.out.print("Enter new group name: "); - String newName = scanner.nextLine().trim(); - - System.out.print("Enter new group description: "); - String newDesc = scanner.nextLine().trim(); - JSONObject req = new JSONObject(); - req.put("action", "edit_group_info"); - req.put("group_id", groupId.toString()); - req.put("name", newName); - req.put("description", newDesc); + req.put("action", "get_chat_info"); + req.put("receiver_id", groupId.toString()); + req.put("receiver_type", "group"); JSONObject res = sendWithResponse(req); - if (res != null) - System.out.println(res.getString("message")); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch group info."); + return; + } + + JSONObject data = res.getJSONObject("data"); + + String currentId = data.getString("id"); + String currentName = data.getString("name"); + String currentDesc = data.optString("description", "None"); + String currentImage = data.optString("image_url", "None"); + + System.out.println("\n--- Current Group Info ---"); + System.out.println("1. Group ID: " + currentId); + System.out.println("2. Name: " + currentName); + System.out.println("3. Description: " + currentDesc); + System.out.println("4. Image URL: " + currentImage); + System.out.println("0. Cancel"); + + System.out.print("Select the field you want to edit (0-4): "); + String choice = scanner.nextLine().trim(); + + String newGroupId = currentId; + String newName = currentName; + String newDesc = currentDesc; + String newImage = currentImage; + + switch (choice) { + case "1" -> { + System.out.print("Enter new Group ID: "); + newGroupId = scanner.nextLine().trim(); + } + case "2" -> { + System.out.print("Enter new Group Name: "); + newName = scanner.nextLine().trim(); + } + case "3" -> { + System.out.print("Enter new Description: "); + newDesc = scanner.nextLine().trim(); + } + case "4" -> { + System.out.print("Enter new Image URL: "); + newImage = scanner.nextLine().trim(); + } + case "0" -> { + System.out.println("Cancelled."); + return; + } + default -> { + System.out.println("Invalid choice."); + return; + } + } + + JSONObject editReq = new JSONObject(); + editReq.put("action", "edit_group_info"); + editReq.put("group_id", groupId.toString()); // internal_uuid + editReq.put("new_group_id", newGroupId); + editReq.put("name", newName); + editReq.put("description", newDesc); + editReq.put("image_url", newImage); + + JSONObject editRes = sendWithResponse(editReq); + if (editRes != null) + System.out.println(editRes.getString("message")); } + private void viewChannelSubscribers(UUID channelId) { JSONObject req = new JSONObject(); req.put("action", "view_channel_subscribers"); diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index d8313a4..1eb550b 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -4,10 +4,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.Group; -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.List; @@ -142,6 +139,48 @@ public class GroupDatabase { } + public static boolean updateGroupInfo(UUID internalUUID, String newGroupId, String name, String description, String imageUrl) { + String sql = "UPDATE groups SET group_id = ?, group_name = ?, description = ?, image_url = ? WHERE internal_uuid = ?"; + + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, newGroupId); + stmt.setString(2, name); + stmt.setString(3, description); + if (imageUrl == null) { + stmt.setNull(4, Types.VARCHAR); + } else { + stmt.setString(4, imageUrl); + } + stmt.setObject(5, internalUUID); + + int affectedRows = stmt.executeUpdate(); + return affectedRows > 0; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean isGroupIdUnique(String groupId, UUID excludeUUID) { + String sql = "SELECT COUNT(*) FROM groups WHERE group_id = ? AND internal_uuid != ?"; + + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, groupId); + stmt.setObject(2, excludeUUID); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return rs.getInt(1) == 0; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + public static void addMember(UUID groupInternalId, UUID userId) { String sql = "INSERT INTO group_members (group_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING"; try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 7eb2de9..4f64bae 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -393,10 +393,10 @@ public class ClientHandler implements Runnable { data.put("internal_id", group.getInternal_uuid().toString()); data.put("name", group.getGroup_name()); data.put("image_url", group.getImage_url()); + data.put("description", group.getDescription() != null ? group.getDescription() : ""); data.put("type", "group"); data.put("id", group.getGroup_id()); - // اضافه کردن owner و admin بودن boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), currentUser.getInternal_uuid()); boolean isAdmin = GroupDatabase.isAdmin(group.getInternal_uuid(), currentUser.getInternal_uuid()); @@ -409,6 +409,7 @@ public class ClientHandler implements Runnable { } + case "channel" -> { Channel channel = ChannelDatabase.findByChannelId(id); if (channel != null) { @@ -627,6 +628,44 @@ public class ClientHandler implements Runnable { } + case "edit_group_info": { + try { + UUID groupUUID = UUID.fromString(requestJson.getString("group_id")); // internal_uuid + String newGroupId = requestJson.getString("new_group_id").trim(); // شناسه نمایشی جدید + String name = requestJson.optString("name"); + String description = requestJson.optString("description", null); + String imageUrl = requestJson.has("image_url") && !requestJson.isNull("image_url") + ? requestJson.getString("image_url") : null; + + boolean isOwner = GroupDatabase.isOwner(groupUUID, currentUser.getInternal_uuid()); + if (!isOwner && !GroupPermissionUtil.canEditGroup(groupUUID, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You don't have permission to edit this group."); + break; + } + + if (isOwner && !GroupDatabase.isGroupIdUnique(newGroupId, groupUUID)) { + response = new ResponseModel("error", "Group ID is already taken by another group."); + break; + } + + boolean updated = GroupDatabase.updateGroupInfo(groupUUID, newGroupId, name, description, imageUrl); + response = updated + ? new ResponseModel("success", "Group info updated successfully.") + : new ResponseModel("error", "Failed to update group info."); + + //if (updated) { + //RealTimeEventDispatcher.sendGroupOrChannelUpdate(groupUUID, "group", name); + //} + + } catch (Exception e) { + response = new ResponseModel("error", "Error updating group: " + e.getMessage()); + } + break; + } + + + + case "view_channel_admins": { UUID channelId = UUID.fromString(requestJson.getString("channel_id")); From 67f48cd943ec2e17a8a03b44f97073454a324109 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 1 Jul 2025 17:18:29 +0330 Subject: [PATCH 7/8] Delete group by owner, transfer ownership, leave group --- .../Client/ActionHandler.java | 77 +++++++++++++++++-- .../Database/ChannelDatabase.java | 17 ++++ .../Database/GroupDatabase.java | 60 ++++++++++++++- .../Server/ClientHandler.java | 70 +++++++++++++++++ 4 files changed, 216 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 7980f72..bd914e2 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -713,8 +713,11 @@ public class ActionHandler { } if (isOwner) { System.out.println("8. Delete Group"); + System.out.println("9. Leave Group (Transfer ownership required)"); + } else { + System.out.println("9. Leave Group"); } - System.out.println("9. Leave Group"); + System.out.println("0. Back to Chat List"); String input = scanner.nextLine(); @@ -747,9 +750,16 @@ public class ActionHandler { return false; } case "9" -> { - leaveChat(chat.getId(), "group"); + if (isOwner) { + transferOwnershipAndLeave(chat.getId()); + refreshChatList(); + } else { + leaveChat(chat.getId(), "group"); + refreshChatList(); + } return false; } + case "0" -> { return false; } @@ -811,6 +821,57 @@ public class ActionHandler { + private void transferOwnershipAndLeave(UUID groupId) { + JSONObject req = new JSONObject(); + req.put("action", "view_group_admins"); + req.put("group_id", groupId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch admins."); + return; + } + + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + + if (admins.length() == 0) { + System.out.println("⚠️ No other admins available. You cannot leave without promoting someone to owner."); + return; + } + + System.out.println("--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + System.out.printf("%d. %s (%s)\n", i + 1, admin.getString("profile_name"), admin.getString("user_id")); + } + + System.out.print("Select a new owner by number: "); + int choice = Integer.parseInt(scanner.nextLine()) - 1; + + if (choice < 0 || choice >= admins.length()) { + System.out.println("Invalid selection."); + return; + } + + JSONObject selected = admins.getJSONObject(choice); + String newOwnerId = selected.getString("user_id"); + + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "transfer_group_ownership"); + promoteReq.put("group_id", groupId.toString()); + promoteReq.put("new_owner_user_id", newOwnerId); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes == null || !promoteRes.getString("status").equals("success")) { + System.out.println("❌ Failed to transfer ownership."); + return; + } + + System.out.println("✅ Ownership transferred successfully."); + leaveChat(groupId, "group"); + } + + private void removeMemberFromGroup(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_members"); @@ -1046,13 +1107,17 @@ public class ActionHandler { JSONObject res = sendWithResponse(req); if (res == null) return; - if (res.getBoolean("success")) { + String status = res.getString("status"); + String message = res.getString("message"); + + if (status.equals("success")) { System.out.println("✅ You left the chat."); } else { - System.out.println("❌ " + res.getString("message")); + System.out.println("❌ " + message); } } + private void sendMessageTo(UUID id, String type) { System.out.print("Enter message: "); String text = scanner.nextLine().trim(); @@ -1120,8 +1185,8 @@ public class ActionHandler { String currentId = data.getString("id"); String currentName = data.getString("name"); - String currentDesc = data.optString("description", "None"); - String currentImage = data.optString("image_url", "None"); + String currentDesc = data.optString("description", null); + String currentImage = data.optString("image_url", null); System.out.println("\n--- Current Group Info ---"); System.out.println("1. Group ID: " + currentId); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index f293efa..668ba34 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -435,5 +435,22 @@ public class ChannelDatabase { } + public static boolean removeSubscriberFromChannel(UUID channelId, UUID userId) { + String sql = "DELETE FROM channel_subscribers WHERE channel_id = ? AND user_id = ?"; + + try (Connection conn =ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + + int affectedRows = stmt.executeUpdate(); + return affectedRows > 0; + + } catch (SQLException e) { + System.err.println("Error removing subscriber from channel: " + e.getMessage()); + return false; + } + } } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 1eb550b..008a147 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -404,22 +404,32 @@ public class GroupDatabase { public static List getGroupAdminsAndOwner(UUID groupId) { - String sql = "SELECT user_id, role, permissions FROM group_members WHERE group_id = ? AND role IN ('owner', 'admin')"; List admins = new ArrayList<>(); + + String sql = "SELECT gm.user_id, gm.role, gm.permissions, u.profile_name " + + "FROM group_members gm " + + "JOIN users u ON gm.user_id = u.internal_uuid " + + "WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin')"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); ResultSet rs = stmt.executeQuery(); + while (rs.next()) { JSONObject obj = new JSONObject(); obj.put("user_id", rs.getObject("user_id").toString()); obj.put("role", rs.getString("role")); obj.put("permissions", new JSONObject(rs.getString("permissions"))); + obj.put("profile_name", rs.getString("profile_name")); admins.add(obj); } - } catch (Exception e) { + + } catch (SQLException e) { e.printStackTrace(); } + return admins; } @@ -544,4 +554,50 @@ public class GroupDatabase { } + public static boolean transferOwnership(UUID groupId, UUID newOwnerId) { + String demoteOldOwner = "UPDATE group_members SET role = 'admin' WHERE group_id = ? AND role = 'owner'"; + String promoteNewOwner = "UPDATE group_members SET role = 'owner' WHERE group_id = ? AND user_id = ?"; + + try (Connection conn = ConnectionDb.connect()) { + conn.setAutoCommit(false); + + try (PreparedStatement demoteStmt = conn.prepareStatement(demoteOldOwner); + PreparedStatement promoteStmt = conn.prepareStatement(promoteNewOwner)) { + + demoteStmt.setObject(1, groupId); + demoteStmt.executeUpdate(); + + promoteStmt.setObject(1, groupId); + promoteStmt.setObject(2, newOwnerId); + promoteStmt.executeUpdate(); + + conn.commit(); + return true; + } catch (SQLException e) { + conn.rollback(); + e.printStackTrace(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + public static boolean deleteGroup(UUID groupId) { + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement("DELETE FROM groups WHERE internal_uuid = ?")) { + + stmt.setObject(1, groupId); + int affectedRows = stmt.executeUpdate(); + + return affectedRows > 0; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 4f64bae..f3bf40b 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -912,6 +912,36 @@ public class ClientHandler implements Runnable { + case "transfer_group_ownership": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID newOwnerUserId = UUID.fromString(requestJson.getString("new_owner_user_id")); + + User newOwner = userDatabase.findByInternalUUID(newOwnerUserId); + if (newOwner == null) { + response = new ResponseModel("error", "New owner not found."); + break; + } + + if (!GroupDatabase.isOwner(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "Only current owner can transfer ownership."); + break; + } + + if (!GroupDatabase.isAdmin(groupId, newOwner.getInternal_uuid())) { + response = new ResponseModel("error", "Selected user is not an admin."); + break; + } + + boolean success = GroupDatabase.transferOwnership(groupId, newOwner.getInternal_uuid()); + + response = success + ? new ResponseModel("success", "Ownership transferred.") + : new ResponseModel("error", "Failed to transfer ownership."); + + break; + } + + case "view_group_members" : { UUID groupId = UUID.fromString(requestJson.getString("group_id")); @@ -948,7 +978,47 @@ public class ClientHandler implements Runnable { } + case "leave_chat": { + String chatType = requestJson.getString("chat_type"); + UUID chatId = UUID.fromString(requestJson.getString("chat_id")); + userId = UUID.fromString(requestJson.getString("user_id")); + boolean success = false; + + switch (chatType) { + case "group" -> success = GroupDatabase.removeMemberFromGroup(chatId, userId); + case "channel" -> success = ChannelDatabase.removeSubscriberFromChannel(chatId, userId); + default -> { + response = new ResponseModel("error", "Unsupported chat type."); + break; + } + } + + if (response == null) { + response = success + ? new ResponseModel("success", "Left the " + chatType + " successfully.") + : new ResponseModel("error", "Failed to leave the " + chatType + "."); + } + + break; + } + + + case "delete_group": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + + if (!GroupDatabase.isOwner(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "Only the group owner can delete the group."); + break; + } + + boolean success = GroupDatabase.deleteGroup(groupId); + + response = success + ? new ResponseModel("success", "Group deleted successfully.") + : new ResponseModel("error", "Failed to delete group."); + break; + } From a62b65b41c1aef05672c3997b90ff327674657be Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Wed, 2 Jul 2025 21:08:50 +0330 Subject: [PATCH 8/8] Channel menu complete --- .../Client/ActionHandler.java | 565 ++++++++++++++++-- .../Client/TelegramClient.java | 1 - .../Database/ChannelDatabase.java | 166 ++++- .../Server/ClientHandler.java | 232 ++++++- .../Utils/ChannelPermissionUtil.java | 54 ++ 5 files changed, 930 insertions(+), 88 deletions(-) create mode 100644 src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index bd914e2..1ec86bd 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -525,6 +525,7 @@ public class ActionHandler { refreshChatList(); System.out.println("✅ Created and opening chat..."); + refreshChatList(); openChat(chat); } @@ -772,42 +773,119 @@ public class ActionHandler { private boolean showChannelChatMenu(ChatEntry chat) { + chat = fetchChatInfo(chat.getId().toString(), chat.getType()); boolean isAdmin = chat.isAdmin(); boolean isOwner = chat.isOwner(); + JSONObject perms = getChannelPermissions(chat.getId()); + System.out.println("\n--- Channel Menu ---"); - if (isOwner || isAdmin) + if (isOwner || (isAdmin && perms.optBoolean("can_post", false))) { System.out.println("1. Send Post"); - - System.out.println("2. View Subscribers"); - - if (isOwner || isAdmin) { - System.out.println("3. Add Admin"); - System.out.println("4. Edit Channel Info"); } - System.out.println("5. Unsubscribe"); + if (isOwner || isAdmin) { + System.out.println("2. View Subscribers"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) { + System.out.println("3. Add Subscriber"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_remove_members", false))) { + System.out.println("4. Remove Subscriber"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_edit_channel", false))) { + System.out.println("5. Edit Channel Info"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_add_admins", false))) { + System.out.println("6. Add Admin"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) { + System.out.println("7. Remove Admin"); + } + + if (isOwner) { + System.out.println("8. Delete Channel"); + System.out.println("9. Leave Channel (Transfer ownership required)"); + } else { + System.out.println("8. Leave Channel"); + } + System.out.println("0. Back to Chat List"); String input = scanner.nextLine(); switch (input) { case "1" -> { - if (isOwner || isAdmin) sendMessageTo(chat.getId(), "channel"); - else System.out.println("❌ You are not allowed to post."); + if (isOwner || (isAdmin && perms.optBoolean("can_post", false))) { + sendMessageTo(chat.getId(), "channel"); + } else { + System.out.println("❌ You don't have permission to post."); + } + } + case "2" -> { + if (isOwner || isAdmin) { + viewChannelSubscribers(chat.getId()); + } else { + System.out.println("❌ You don't have permission to view subscribers."); + } } - case "2" -> viewChannelSubscribers(chat.getId()); case "3" -> { - if (isOwner) addAdminToChannel(chat.getId()); - else System.out.println("❌ Only owner can add admins."); + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) { + searchEligibleUsers("channel", chat.getId()); + } else { + System.out.println("❌ You don't have permission to add subscribers."); + } } case "4" -> { - if (isOwner || isAdmin) editChannelInfo(chat.getId()); - else System.out.println("❌ You don't have permission."); + if (isOwner || (isAdmin && perms.optBoolean("can_remove_members", false))) { + removeSubscriberFromChannel(chat.getId()); + } else { + System.out.println("❌ You don't have permission to remove subscribers."); + } } case "5" -> { - leaveChat(chat.getId(), "channel"); - return false; + if (isOwner || (isAdmin && perms.optBoolean("can_edit_channel", false))) { + editChannelInfo(chat.getId()); + } else { + System.out.println("❌ You don't have permission to edit channel info."); + } + } + case "6" -> { + if (isOwner || (isAdmin && perms.optBoolean("can_add_admins", false))) { + addAdminToChannel(chat.getId()); + } else { + System.out.println("❌ You don't have permission to add admins."); + } + } + case "7" -> { + if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) { + removeAdminFromChannel(chat.getId()); + } else { + System.out.println("❌ You don't have permission to remove admins."); + } + } + case "8" -> { + if (isOwner) { + deleteChannel(chat.getId()); + return false; + } else { + leaveChat(chat.getId(), "channel"); + return false; + } + } + case "9" -> { + if (isOwner) { + transferChannelOwnershipAndLeave(chat.getId()); + refreshChatList(); + return false; + } else { + System.out.println("❌ You don't have permission."); + } } case "0" -> { return false; @@ -821,6 +899,9 @@ public class ActionHandler { + + + private void transferOwnershipAndLeave(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_admins"); @@ -893,7 +974,6 @@ public class ActionHandler { String profileName = m.getString("profile_name"); String userId = m.getString("user_id"); - // فقط اونر غیرقابل حذف if (role.equals("owner")) continue; eligible.add(m); @@ -934,6 +1014,82 @@ public class ActionHandler { + private void addAdminToChannel(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_subscribers"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch subscribers."); + return; + } + + JSONArray subscribers = res.getJSONObject("data").getJSONArray("subscribers"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Subscribers List ---"); + for (int i = 0; i < subscribers.length(); i++) { + JSONObject s = subscribers.getJSONObject(i); + String role = s.getString("role"); + String profileName = s.getString("profile_name"); + String userId = s.getString("user_id"); + + if (role.equals("subscriber")) { + eligible.add(s); + System.out.printf("%d. %s (%s)\n", eligible.size(), profileName, userId); + } + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No eligible subscribers to promote."); + return; + } + + System.out.print("Select a subscriber to promote to admin: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("internal_uuid"); + + JSONObject permissions = new JSONObject(); + System.out.print("Can post? (true/false): "); + permissions.put("can_post", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can edit channel info? (true/false): "); + permissions.put("can_edit_channel", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add members? (true/false): "); + permissions.put("can_add_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove members? (true/false): "); + permissions.put("can_remove_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add admins? (true/false): "); + permissions.put("can_add_admins", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove admins? (true/false): "); + permissions.put("can_remove_admins", Boolean.parseBoolean(scanner.nextLine())); + + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "add_admin_to_channel"); + promoteReq.put("channel_id", channelId.toString()); + promoteReq.put("target_user_id", targetInternalUUID); + promoteReq.put("permissions", permissions); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes != null) + System.out.println(promoteRes.getString("message")); + } + + + private void addAdminToGroup(UUID groupId) { JSONObject req = new JSONObject(); @@ -1038,6 +1194,142 @@ public class ActionHandler { } + + private void removeSubscriberFromChannel(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_subscribers"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch subscribers."); + return; + } + + JSONArray subscribers = res.getJSONObject("data").getJSONArray("subscribers"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Subscribers List ---"); + for (int i = 0; i < subscribers.length(); i++) { + JSONObject s = subscribers.getJSONObject(i); + String role = s.getString("role"); + String profileName = s.getString("profile_name"); + String userId = s.getString("user_id"); + + if (role.equals("owner")) continue; + + eligible.add(s); + System.out.println((eligible.size()) + ". " + profileName + " (" + userId + ") [" + role + "]"); + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No removable subscribers."); + return; + } + + System.out.print("Select a subscriber to remove: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("internal_uuid"); + + JSONObject removeReq = new JSONObject(); + removeReq.put("action", "remove_subscriber_from_channel"); + removeReq.put("channel_id", channelId.toString()); + removeReq.put("user_id", targetInternalUUID); + + JSONObject removeRes = sendWithResponse(removeReq); + if (removeRes != null) + System.out.println(removeRes.getString("message")); + } + + + private void transferChannelOwnershipAndLeave(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_admins"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch admins."); + return; + } + + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + + if (admins.length() == 0) { + System.out.println("⚠️ No other admins available. You cannot leave without promoting someone to owner."); + return; + } + + System.out.println("\n--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + System.out.printf("%d. %s (%s)\n", i + 1, admin.getString("profile_name"), admin.getString("user_id")); + } + + System.out.print("Select a new owner by number: "); + int choice = Integer.parseInt(scanner.nextLine()) - 1; + + if (choice < 0 || choice >= admins.length()) { + System.out.println("Invalid selection."); + return; + } + + JSONObject selected = admins.getJSONObject(choice); + String newOwnerId = selected.getString("user_id"); + + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "transfer_channel_ownership"); + promoteReq.put("channel_id", channelId.toString()); + promoteReq.put("new_owner_user_id", newOwnerId); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes == null || !promoteRes.getString("status").equals("success")) { + System.out.println("❌ Failed to transfer ownership."); + return; + } + + System.out.println("✅ Ownership transferred successfully."); + leaveChat(channelId, "channel"); + } + + + + private void deleteChannel(UUID channelId) { + System.out.print("Are you sure you want to delete the channel? (yes/no): "); + String confirm = scanner.nextLine().trim().toLowerCase(); + + if (!confirm.equals("yes")) { + System.out.println("❌ Delete cancelled."); + return; + } + + JSONObject req = new JSONObject(); + req.put("action", "delete_channel"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res != null && res.getString("status").equals("success")) { + System.out.println("✅ Channel deleted successfully."); + refreshChatList(); + } else { + System.out.println("❌ Failed to delete channel."); + } + } + + private void deleteGroup(UUID groupId) { System.out.print("Are you sure you want to delete the group? (yes/no): "); String confirm = scanner.nextLine().trim().toLowerCase(); @@ -1118,6 +1410,68 @@ public class ActionHandler { } + private void removeAdminFromChannel(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_admins"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch admins."); + return; + } + + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + String role = admin.getString("role"); + String profileName = admin.getString("profile_name"); + String userId = admin.getString("user_id"); + + if (!role.equals("owner")) { + eligible.add(admin); + System.out.printf("%d. %s (%s)\n", eligible.size(), profileName, userId); + } + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No removable admins."); + return; + } + + System.out.print("Select an admin to remove: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("internal_uuid"); + + JSONObject removeReq = new JSONObject(); + removeReq.put("action", "remove_admin_from_channel"); + removeReq.put("channel_id", channelId.toString()); + removeReq.put("target_user_id", targetInternalUUID); + + JSONObject removeRes = sendWithResponse(removeReq); + if (removeRes != null) + System.out.println(removeRes.getString("message")); + } + + + + private void sendMessageTo(UUID id, String type) { System.out.print("Enter message: "); String text = scanner.nextLine().trim(); @@ -1156,15 +1510,19 @@ public class ActionHandler { } } - private void addSubscriberToChannel(UUID channelId, UUID userId) { + private void addSubscriberToChannel(UUID channelId, UUID targetUserId) { JSONObject req = new JSONObject(); req.put("action", "add_subscriber_to_channel"); req.put("channel_id", channelId.toString()); - req.put("user_id", userId.toString()); + req.put("user_id", targetUserId.toString()); JSONObject res = sendWithResponse(req); - if (res != null) { - System.out.println(res.getString("message")); + if (res == null) return; + + if (res.getString("status").equals("success")) { + System.out.println("✅ Subscriber added successfully."); + } else { + System.out.println("❌ " + res.getString("message")); } } @@ -1253,8 +1611,10 @@ public class ActionHandler { JSONObject res = sendWithResponse(req); if (res == null) return; - if (res.getBoolean("success")) { - JSONArray subs = res.getJSONArray("subscribers"); + if (res.getString("status").equals("success")) { + JSONObject data = res.getJSONObject("data"); + JSONArray subs = data.getJSONArray("subscribers"); + System.out.println("--- Subscribers ---"); for (int i = 0; i < subs.length(); i++) { JSONObject s = subs.getJSONObject(i); @@ -1265,36 +1625,81 @@ public class ActionHandler { } } - private void addAdminToChannel(UUID channelId) { - System.out.print("Enter user_id to promote: "); - String userId = scanner.nextLine().trim(); + + + private void editChannelInfo(UUID channelInternalId) { JSONObject req = new JSONObject(); - req.put("action", "add_admin_to_channel"); - req.put("channel_id", channelId.toString()); - req.put("user_id", userId); + req.put("action", "get_chat_info"); + req.put("receiver_id", channelInternalId.toString()); + req.put("receiver_type", "channel"); JSONObject res = sendWithResponse(req); - if (res != null) - System.out.println(res.getString("message")); - } + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch channel info."); + return; + } - private void editChannelInfo(UUID channelId) { - System.out.print("Enter new channel name: "); - String name = scanner.nextLine().trim(); + JSONObject data = res.getJSONObject("data"); - System.out.print("Enter new description: "); - String desc = scanner.nextLine().trim(); + String currentId = data.getString("id"); + String currentName = data.getString("name"); + String currentDesc = data.optString("description", null); + String currentImage = data.optString("image_url", null); - JSONObject req = new JSONObject(); - req.put("action", "edit_channel_info"); - req.put("channel_id", channelId.toString()); - req.put("name", name); - req.put("description", desc); + System.out.println("\n--- Current Channel Info ---"); + System.out.println("1. Channel ID: " + currentId); + System.out.println("2. Name: " + currentName); + System.out.println("3. Description: " + currentDesc); + System.out.println("4. Image URL: " + currentImage); + System.out.println("0. Cancel"); - JSONObject res = sendWithResponse(req); - if (res != null) - System.out.println(res.getString("message")); + System.out.print("Select the field you want to edit (0-4): "); + String choice = scanner.nextLine().trim(); + + String newChannelId = currentId; + String newName = currentName; + String newDesc = currentDesc; + String newImage = currentImage; + + switch (choice) { + case "1" -> { + System.out.print("Enter new Channel ID: "); + newChannelId = scanner.nextLine().trim(); + } + case "2" -> { + System.out.print("Enter new Channel Name: "); + newName = scanner.nextLine().trim(); + } + case "3" -> { + System.out.print("Enter new Description: "); + newDesc = scanner.nextLine().trim(); + } + case "4" -> { + System.out.print("Enter new Image URL: "); + newImage = scanner.nextLine().trim(); + } + case "0" -> { + System.out.println("Cancelled."); + return; + } + default -> { + System.out.println("Invalid choice."); + return; + } + } + + JSONObject editReq = new JSONObject(); + editReq.put("action", "edit_channel_info"); + editReq.put("channel_id", channelInternalId.toString()); // internal_uuid + editReq.put("new_channel_id", newChannelId); + editReq.put("name", newName); + editReq.put("description", newDesc); + editReq.put("image_url", newImage); + + JSONObject editRes = sendWithResponse(editReq); + if (editRes != null) + System.out.println(editRes.getString("message")); } @@ -1395,6 +1800,74 @@ public class ActionHandler { } + private void editChannelAdminPermissions(UUID channelId) { + System.out.print("Enter user_id of the admin to edit: "); + String userId = scanner.nextLine().trim(); + + JSONObject permissions = new JSONObject(); + System.out.print("Can post? (true/false): "); + permissions.put("can_post", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can edit channel info? (true/false): "); + permissions.put("can_edit_channel", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add members? (true/false): "); + permissions.put("can_add_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove members? (true/false): "); + permissions.put("can_remove_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add admins? (true/false): "); + permissions.put("can_add_admins", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove admins? (true/false): "); + permissions.put("can_remove_admins", Boolean.parseBoolean(scanner.nextLine())); + + JSONObject req = new JSONObject(); + req.put("action", "edit_channel_admin_permissions"); + req.put("channel_id", channelId.toString()); + req.put("user_id", userId); + req.put("permissions", permissions); + + JSONObject res = sendWithResponse(req); + if (res != null) + System.out.println(res.getString("message")); + } + + + private void viewChannelAdmins(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_admins"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + if (res.getString("status").equals("success")) { + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + + System.out.println("\n--- Channel Admins ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject a = admins.getJSONObject(i); + System.out.printf("- %s (%s) [%s]\n", + a.getString("profile_name"), + a.getString("user_id"), + a.getString("role")); + } + } else { + System.out.println("❌ Failed to fetch admins."); + } + } + + + private JSONObject getChannelPermissions(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "get_channel_permissions"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + return new JSONObject(); + } + return res.getJSONObject("data"); + } + + public void editAdminPermissions(String type, UUID entityId) { System.out.print("Enter user ID of the admin to edit: "); String targetUserId = scanner.nextLine().trim(); diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index 16ae524..4a32050 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -34,7 +34,6 @@ public class TelegramClient { System.out.println("✅ Connected to Telegram Server"); this.handler = new ActionHandler(this.out, this.in, this.scanner); - // 👂 فقط این ترد مجاز به خواندن از in است Thread listenerThread = new Thread(new IncomingMessageListener(in)); listenerThread.setDaemon(true); listenerThread.start(); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index 668ba34..efa8535 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -1,5 +1,6 @@ package org.to.telegramfinalproject.Database; +import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.Channel; import org.to.telegramfinalproject.Models.Group; @@ -364,22 +365,36 @@ public class ChannelDatabase { public static List getChannelAdminsAndOwner(UUID channelId) { - String sql = "SELECT user_id, role, permissions FROM channel_subscribers WHERE channel_id = ? AND role IN ('owner', 'admin')"; List admins = new ArrayList<>(); + + String sql = """ + SELECT u.internal_uuid, u.profile_name, u.user_id, cs.role, cs.permissions + FROM channel_subscribers cs + JOIN users u ON cs.user_id = u.internal_uuid + WHERE cs.channel_id = ? AND (cs.role = 'owner' OR cs.role = 'admin') + """; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); - ResultSet rs = stmt.executeQuery(); - while (rs.next()) { - JSONObject obj = new JSONObject(); - obj.put("user_id", rs.getObject("user_id").toString()); - obj.put("role", rs.getString("role")); - obj.put("permissions", new JSONObject(rs.getString("permissions"))); - admins.add(obj); + + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + JSONObject obj = new JSONObject(); + obj.put("internal_uuid", rs.getObject("internal_uuid").toString()); + obj.put("profile_name", rs.getString("profile_name")); + obj.put("user_id", rs.getString("user_id")); + obj.put("role", rs.getString("role")); + obj.put("permissions", new JSONObject(rs.getString("permissions"))); + admins.add(obj); + } } - } catch (Exception e) { + + } catch (SQLException e) { e.printStackTrace(); } + return admins; } @@ -452,5 +467,138 @@ public class ChannelDatabase { return false; } } + public static JSONArray getChannelSubscribers(UUID channelId) { + JSONArray subscribers = new JSONArray(); + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement( + "SELECT u.internal_uuid, u.user_id, u.profile_name, " + + "CASE WHEN cs.role = 'owner' THEN 'owner' " + + " WHEN cs.role = 'admin' THEN 'admin' " + + " ELSE 'subscriber' END AS role " + + "FROM channel_subscribers cs " + + "JOIN users u ON cs.user_id = u.internal_uuid " + + "WHERE cs.channel_id = ?")) { + + stmt.setObject(1, channelId); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + JSONObject obj = new JSONObject(); + obj.put("internal_uuid", rs.getObject("internal_uuid").toString()); + obj.put("user_id", rs.getString("user_id")); + obj.put("profile_name", rs.getString("profile_name")); + obj.put("role", rs.getString("role")); + subscribers.put(obj); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return subscribers; + } + + + + public static boolean updateChannelInfo(UUID channelId, String newId, String name, String description, String imageUrl) { + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement( + "UPDATE channels SET channel_id = ?, channel_name = ?, description = ?, image_url = ? WHERE internal_uuid = ?")) { + + stmt.setString(1, newId); + stmt.setString(2, name); + stmt.setString(3, description); + stmt.setString(4, imageUrl); + stmt.setObject(5, channelId); + + int rows = stmt.executeUpdate(); + return rows > 0; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean isChannelIdUnique(String channelId, UUID excludeChannelUUID) { + String query = "SELECT COUNT(*) FROM channels WHERE channel_id = ? AND internal_uuid != ?"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(query)) { + + stmt.setString(1, channelId); + stmt.setObject(2, excludeChannelUUID); + + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + int count = rs.getInt(1); + return count == 0; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + public static boolean demoteAdminToSubscriber(UUID channelId, UUID userId) { + String sql = "UPDATE channel_subscribers SET role = 'member', permissions = '{}'::jsonb WHERE channel_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + + int affected = stmt.executeUpdate(); + return affected > 0; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static boolean deleteChannel(UUID channelId) { + String sql = "DELETE FROM channels WHERE internal_uuid = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, channelId); + int affected = stmt.executeUpdate(); + return affected > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean transferOwnership(UUID channelId, UUID newOwnerUUID) { + String sql = """ + UPDATE channel_subscribers + SET role = CASE + WHEN user_id = ? THEN 'owner' + WHEN role = 'owner' THEN 'admin' + ELSE role + END + WHERE channel_id = ? + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, newOwnerUUID); + stmt.setObject(2, channelId); + + stmt.executeUpdate(); + return true; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index f3bf40b..76022b6 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -4,6 +4,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Database.*; import org.to.telegramfinalproject.Models.*; +import org.to.telegramfinalproject.Utils.ChannelPermissionUtil; import org.to.telegramfinalproject.Utils.GroupPermissionUtil; import java.io.*; @@ -411,19 +412,27 @@ public class ClientHandler implements Runnable { case "channel" -> { - Channel channel = ChannelDatabase.findByChannelId(id); + Channel channel = ChannelDatabase.findByInternalUUID(UUID.fromString(id)); if (channel != null) { data.put("internal_id", channel.getInternal_uuid().toString()); data.put("name", channel.getChannel_name()); data.put("image_url", channel.getImage_url()); + data.put("description", channel.getDescription() != null ? channel.getDescription() : ""); data.put("type", "channel"); data.put("id", channel.getChannel_id()); + + boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), currentUser.getInternal_uuid()); + boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), currentUser.getInternal_uuid()); + + data.put("is_owner", isOwner); + data.put("is_admin", isAdmin); } else { response = new ResponseModel("error", "Channel not found."); break; } } + default -> { response = new ResponseModel("error", "Unknown type."); break; @@ -588,27 +597,38 @@ public class ClientHandler implements Runnable { break; } - case "add_admin_to_channel": { - UUID channelId = UUID.fromString(requestJson.getString("channel_id")); - UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); - JSONObject permissions = requestJson.optJSONObject("permissions"); + try { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); + JSONObject permissions = requestJson.optJSONObject("permissions"); - //if (!ChannelPermissionUtil.canAddAdmins(channelId, currentUser.getInternal_uuid())) { - //response = new ResponseModel("error", "You are not allowed to add admins to the channel."); - //break; - //} + if (!ChannelPermissionUtil.canAddAdmins(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to add admins to the channel."); + break; + } + String targetRole = ChannelDatabase.getChannelRole(channelId, targetUserId); + if (targetRole == null) { + response = new ResponseModel("error", "User is not a subscriber of the channel."); + break; + } - boolean success = ChannelDatabase.addAdminToChannel(channelId, targetUserId, permissions); - response = success - ? new ResponseModel("success", "Admin added to channel.") - : new ResponseModel("error", "Failed to add admin."); + if (targetRole.equals("owner") || targetRole.equals("admin")) { + response = new ResponseModel("error", "User is already an admin or owner."); + break; + } + + boolean success = ChannelDatabase.addAdminToChannel(channelId, targetUserId, permissions); + response = success + ? new ResponseModel("success", "Admin added to channel.") + : new ResponseModel("error", "Failed to add admin."); + } catch (Exception e) { + response = new ResponseModel("error", "Error adding admin to channel: " + e.getMessage()); + } break; } - - case "edit_channel_admin_permissions": { UUID channelId = UUID.fromString(requestJson.getString("channel_id")); UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); @@ -724,25 +744,16 @@ public class ClientHandler implements Runnable { - case "remove_admin_from_group": { - UUID groupId = UUID.fromString(requestJson.getString("group_id")); - String targetUserIdStr = requestJson.getString("user_id"); + case "remove_admin_from_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserUUID = UUID.fromString(requestJson.getString("target_user_id")); - - User targetUser = new userDatabase().findByUserId(targetUserIdStr); - if (targetUser == null) { - response = new ResponseModel("error", "User not found."); - break; - } - - UUID targetUserUUID = targetUser.getInternal_uuid(); - - if (!GroupPermissionUtil.canRemoveAdmins(groupId, currentUser.getInternal_uuid())) { + if (!ChannelPermissionUtil.canRemoveAdmins(channelId, currentUser.getInternal_uuid())) { response = new ResponseModel("error", "You are not allowed to remove admins."); break; } - String targetRole = GroupDatabase.getGroupRole(groupId, targetUserUUID); + String targetRole = ChannelDatabase.getChannelRole(channelId, targetUserUUID); if (targetRole.equals("owner")) { response = new ResponseModel("error", "You cannot remove the owner."); break; @@ -752,7 +763,7 @@ public class ClientHandler implements Runnable { break; } - boolean success = GroupDatabase.demoteAdminToMember(groupId, targetUserUUID); + boolean success = ChannelDatabase.demoteAdminToSubscriber(channelId, targetUserUUID); response = success ? new ResponseModel("success", "Admin removed successfully.") : new ResponseModel("error", "Failed to remove admin."); @@ -760,7 +771,6 @@ public class ClientHandler implements Runnable { } - case "add_member_to_group": { UUID groupId = UUID.fromString(requestJson.getString("group_id")); UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); @@ -856,7 +866,7 @@ public class ClientHandler implements Runnable { case "channel" -> { - Channel channel = ChannelDatabase.findByChannelId(receiverId); + Channel channel = ChannelDatabase.findByInternalUUID(UUID.fromString(receiverId)); if (channel == null) { response = new ResponseModel("error", "Channel not found."); break; @@ -1021,6 +1031,164 @@ public class ClientHandler implements Runnable { } + case "get_channel_permissions": { + try { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + userId = currentUser.getInternal_uuid(); + + JSONObject permissions = ChannelDatabase.getChannelPermissions(channelId, userId); + + response = new ResponseModel("success", "Permissions fetched.", permissions); + } catch (Exception e) { + response = new ResponseModel("error", "Error fetching channel permissions: " + e.getMessage()); + } + break; + } + + + case "view_channel_subscribers": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + + boolean isOwner = ChannelDatabase.isOwner(channelId, currentUser.getInternal_uuid()); + boolean isAdmin = ChannelDatabase.isAdmin(channelId, currentUser.getInternal_uuid()); + + if (!isOwner && !isAdmin) { + response = new ResponseModel("error", "You are not authorized to view subscribers."); + break; + } + + JSONArray subscribers = ChannelDatabase.getChannelSubscribers(channelId); + + if (subscribers != null) { + JSONObject data = new JSONObject(); + data.put("subscribers", subscribers); + response = new ResponseModel("success", "Subscribers fetched successfully.", data); + } else { + response = new ResponseModel("error", "Failed to fetch subscribers."); + } + break; + } + + + case "add_subscriber_to_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); + + if (!ChannelPermissionUtil.canAddSubscribers(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to add subscribers."); + break; + } + + if (ChannelDatabase.isUserInChannel(targetUserId, channelId)) { + response = new ResponseModel("error", "User is already a subscriber."); + break; + } + + boolean success = ChannelDatabase.addSubscriberToChannel(targetUserId, channelId); + response = success + ? new ResponseModel("success", "Subscriber added to channel.") + : new ResponseModel("error", "Failed to add subscriber."); + break; + } + + case "remove_subscriber_from_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); + + if (!ChannelPermissionUtil.canRemoveSubscribers(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to remove subscribers."); + break; + } + + String targetRole = ChannelDatabase.getChannelRole(channelId, targetUserId); + if (targetRole == null) { + response = new ResponseModel("error", "User is not a subscriber of the channel."); + break; + } + + if (targetRole.equals("owner")) { + response = new ResponseModel("error", "You cannot remove the owner."); + break; + } + + boolean success = ChannelDatabase.removeSubscriberFromChannel(channelId, targetUserId); + response = success + ? new ResponseModel("success", "Subscriber removed from channel.") + : new ResponseModel("error", "Failed to remove subscriber."); + break; + } + + + case "edit_channel_info": { + try { + UUID channelUUID = UUID.fromString(requestJson.getString("channel_id")); // internal_uuid + String newChannelId = requestJson.getString("new_channel_id").trim(); + String name = requestJson.optString("name"); + String description = requestJson.optString("description", null); + String imageUrl = requestJson.has("image_url") && !requestJson.isNull("image_url") + ? requestJson.getString("image_url") : null; + + boolean isOwner = ChannelDatabase.isOwner(channelUUID, currentUser.getInternal_uuid()); + if (!isOwner && !ChannelPermissionUtil.canEditChannel(channelUUID, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You don't have permission to edit this channel."); + break; + } + + if (isOwner && !ChannelDatabase.isChannelIdUnique(newChannelId, channelUUID)) { + response = new ResponseModel("error", "Channel ID is already taken by another channel."); + break; + } + + boolean updated = ChannelDatabase.updateChannelInfo(channelUUID, newChannelId, name, description, imageUrl); + response = updated + ? new ResponseModel("success", "Channel info updated successfully.") + : new ResponseModel("error", "Failed to update channel info."); + + } catch (Exception e) { + response = new ResponseModel("error", "Error updating channel: " + e.getMessage()); + } + break; + } + + + case "delete_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + + if (!ChannelDatabase.isOwner(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "Only the owner can delete the channel."); + break; + } + + boolean success = ChannelDatabase.deleteChannel(channelId); + response = success + ? new ResponseModel("success", "Channel deleted successfully.") + : new ResponseModel("error", "Failed to delete channel."); + + break; + } + + case "transfer_channel_ownership": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + String newOwnerUserIdStr = requestJson.getString("new_owner_user_id"); + + User newOwner = new userDatabase().findByUserId(newOwnerUserIdStr); + if (newOwner == null) { + response = new ResponseModel("error", "User not found."); + break; + } + + if (!ChannelDatabase.isOwner(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "Only the owner can transfer ownership."); + break; + } + + boolean success = ChannelDatabase.transferOwnership(channelId, newOwner.getInternal_uuid()); + response = success + ? new ResponseModel("success", "Ownership transferred successfully.") + : new ResponseModel("error", "Failed to transfer ownership."); + break; + } + default: diff --git a/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java b/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java new file mode 100644 index 0000000..fbdc5ad --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java @@ -0,0 +1,54 @@ +package org.to.telegramfinalproject.Utils; + +import org.json.JSONObject; +import org.to.telegramfinalproject.Database.ChannelDatabase; + +import java.util.UUID; + +public class ChannelPermissionUtil { + + public static boolean canAddSubscribers(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_add_members", false); + } + return false; + } + + public static boolean canAddAdmins(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_add_admins", false); + } + return false; + } + + public static boolean canEditChannel(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_edit_channel", false); + } + return false; + } + + public static boolean canRemoveAdmins(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_remove_admins", false); + } + return false; + } + + public static boolean canRemoveSubscribers(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_remove_members", false); + } + return false; + } +}