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 2555c2c..1ec86bd 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -1,18 +1,17 @@ package org.to.telegramfinalproject.Client; -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.io.BufferedReader; +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; @@ -26,41 +25,44 @@ 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,85 +73,181 @@ public class ActionHandler { String userId = Session.currentUser.getString("user_id"); SearchRequestModel model = new SearchRequestModel("search", keyword, userId); - send(model.toJson()); } - private void send(JSONObject request) { + + 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()); + + } + + + 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 { - if (!request.has("action") || request.isNull("action")) { - System.err.println("Error: Request does not contain 'action'."); - return; - } - - 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 { - System.out.println("No response from server."); - } - - } catch (IOException e) { - System.err.println("Error while communicating with server: " + e.getMessage()); + choice = Integer.parseInt(scanner.nextLine()) - 1; } catch (Exception e) { - System.err.println("Client error: " + e.getMessage()); + 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"); + req.put("user_id", Session.currentUser.getString("user_id")); + req.put("contact_id", contactId.toString()); + send(req); + } + + + private void joinGroupOrChannel(String type, String uuid) { + JSONObject req = new JSONObject(); + req.put("action", "join_" + type); + req.put("user_id", Session.getUserUUID()); + req.put("id", uuid); + send(req); + } + + + + 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 { + 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()); + } + + // اگر شکست خورد، internalId نامعتبر می‌سازیم (برای جلوگیری از null) + return new ChatEntry( + UUID.randomUUID(), // ساخت یک UUID موقت (ولی اشتباه) + 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 { + 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.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("❌ Error during refreshChatList: " + e.getMessage()); e.printStackTrace(); } } @@ -157,38 +255,336 @@ public class ActionHandler { - public void userMenu() { - while (true) { - System.out.println("\nUser Menu:"); - System.out.println("1. Show chat list"); - System.out.println("2. Search"); - System.out.println("3. Logout"); + 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(); - System.out.print("Choose an option: "); - String choice = scanner.nextLine(); + JSONObject req = new JSONObject(); + req.put("action", "create_group"); + req.put("user_id", Session.getUserUUID()); + req.put("group_id", groupId); + req.put("group_name", groupName); + req.put("image_url", imageUrl.isBlank() ? JSONObject.NULL : imageUrl); - switch (choice) { - - case "1": - showChatListAndSelect(); - break; - case "2" : - search(); - break; - - case "3": - - logout(); - - return; - default: - System.out.println("Invalid choice."); - } - } + 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); + } + + 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); + } + + + + + private void send(JSONObject request) { + try { + if (!request.has("action") || request.isNull("action")) { + System.err.println("❌ Invalid request: missing action."); + return; + } + + 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; + } + + 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( + 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.optBoolean("is_owner", false), + chat.optBoolean("is_admin", false) + ); + + + + chatList.add(entry); + } + + + Session.chatList = chatList; + break; + + 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()) { + 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 (or 0 to exit): "); + int index = Integer.parseInt(scanner.nextLine()) - 1; + 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 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(uuid) && c.getType().equals("private")) + .findFirst() + .orElse(null); + + if (existing != null) { + openChat(existing); + } else { + System.out.println("ℹ Trying to add contact..."); + addContact(uuid); + + refreshChatList(); + 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 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(uuid) && c.getType().equals(type)) + .findFirst() + .orElse(null); + + if (existing != null) { + openChat(existing); + } else { + System.out.println("ℹ Trying to join " + type + "..."); + joinGroupOrChannel(type, uuidStr); + + refreshChatList(); + 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.getDisplayId().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 (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, + chatJson.optBoolean("is_owner", false), + chatJson.optBoolean("is_admin", false) + ); + + + refreshChatList(); + System.out.println("✅ Created and opening chat..."); + refreshChatList(); + 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 "logout": + Session.currentUser = null; + Session.chatList = null; + break; + } + + } catch (Exception e) { + System.err.println("❌ Error during send(): " + e.getMessage()); + e.printStackTrace(); + } + } + + + 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. 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" -> createChannel(); + case "4" -> createGroup(); + case "5" -> { + logout(); + return; + } + default -> System.out.println("Invalid choice."); + } + } + } public void showChatListAndSelect() { if (Session.chatList == null || Session.chatList.isEmpty()) { @@ -199,14 +595,21 @@ 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: "); 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; } @@ -216,23 +619,1105 @@ 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); + private void openChat(ChatEntry chat) { + 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 || (isAdmin && perms.optBoolean("can_remove_members", false))) { + System.out.println("7. Remove member"); + } + 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("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))) + searchEligibleUsers("group", 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 || (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 "9" -> { + if (isOwner) { + transferOwnershipAndLeave(chat.getId()); + refreshChatList(); + } else { + leaveChat(chat.getId(), "group"); + refreshChatList(); + } + return false; + } + + case "0" -> { + return false; + } + default -> System.out.println("Invalid choice."); + } + return true; + } + + + + + 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 && perms.optBoolean("can_post", false))) { + System.out.println("1. Send Post"); + } + + 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 && 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 "3" -> { + 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 && perms.optBoolean("can_remove_members", false))) { + removeSubscriberFromChannel(chat.getId()); + } else { + System.out.println("❌ You don't have permission to remove subscribers."); + } + } + case "5" -> { + 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; + } + default -> System.out.println("Invalid choice."); + } + return true; + } + + + + + + + + + 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"); + 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")); + } + + + + 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(); + 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 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(); + + 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; + + 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("❌ " + message); + } + } + + + 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(); + + if (text.isEmpty()) { + System.out.println("Message cannot be empty."); + return; + } + + JSONObject req = new JSONObject(); + req.put("action", "send_message"); + req.put("sender_id", Session.getUserUUID()); + req.put("receiver_id", id.toString()); + req.put("receiver_type", type); + req.put("text", text); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + if (res.getBoolean("success")) { + System.out.println("✅ Message sent."); + } else { + System.out.println("❌ Failed to send message."); + } + } + + private void addMemberToGroup(UUID groupId, UUID userId) { + JSONObject req = new JSONObject(); + req.put("action", "add_member_to_group"); + req.put("group_id", groupId.toString()); + req.put("user_id", userId.toString()); + + JSONObject res = sendWithResponse(req); + if (res != null) { + System.out.println(res.getString("message")); + } + } + + 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", targetUserId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + if (res.getString("status").equals("success")) { + System.out.println("✅ Subscriber added successfully."); + } else { + System.out.println("❌ " + res.getString("message")); + } + } + + + private void editGroupInfo(UUID groupId) { + JSONObject req = new JSONObject(); + req.put("action", "get_chat_info"); + req.put("receiver_id", groupId.toString()); + req.put("receiver_type", "group"); + + JSONObject res = sendWithResponse(req); + 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", null); + String currentImage = data.optString("image_url", null); + + 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"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + 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); + System.out.println("- " + s.getString("profile_name")); + } + } else { + System.out.println("❌ Failed to load subscribers."); + } + } + + + + + private void editChannelInfo(UUID channelInternalId) { + JSONObject req = new JSONObject(); + req.put("action", "get_chat_info"); + req.put("receiver_id", channelInternalId.toString()); + req.put("receiver_type", "channel"); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch channel info."); + return; + } + + JSONObject data = res.getJSONObject("data"); + + String currentId = data.getString("id"); + String currentName = data.getString("name"); + String currentDesc = data.optString("description", null); + String currentImage = data.optString("image_url", null); + + 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"); + + 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")); + } + + + 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(); - String userId = Session.currentUser.getString("internalUUID"); request.put("action", "logout"); - request.put("user_id",userId); + request.put("user_id", Session.currentUser.getString("internal_uuid")); + send(request); Session.currentUser = null; Session.chatList = null; @@ -241,5 +1726,220 @@ 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")); + } + + + 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(); + + 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 new file mode 100644 index 0000000..30ca3a0 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Client/IncomingMessageListener.java @@ -0,0 +1,121 @@ +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 { + 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")) { + String action = response.getString("action"); + if (isRealTimeEvent(action)) { + handleRealTimeEvent(response); + } else { + TelegramClient.responseQueue.put(response); + } + } 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/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/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index 6897bfc..4a32050 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -8,15 +8,19 @@ import java.io.InputStreamReader; 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"; - private static final int SERVER_PORT = 12345; + private static final int SERVER_PORT = 8000; private Socket socket; private BufferedReader in; private PrintWriter out; private final Scanner scanner; ActionHandler handler = null; + public static BlockingQueue responseQueue = new LinkedBlockingQueue<>(); public TelegramClient() { this.scanner = new Scanner(System.in); @@ -27,72 +31,51 @@ 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()); - } + 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."); - this.handler.userMenu(); - + 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 8f8de92..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,7 @@ 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; @@ -7,6 +9,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; @@ -75,4 +78,527 @@ public class ChannelDatabase { } return result; } + + + + public static List getSubscriberUUIDs(UUID channelInternalUUID) { + List subscriberIds = new ArrayList<>(); + String sql = "SELECT user_id FROM channel_subscribers 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_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); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + + + 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; + } + } + + + 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 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(); + } + } + + + 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; + } + + + 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) { + 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); + + 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 (SQLException 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; + } + + + 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; + } + } + + + 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; + } + } + 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/Database/ContactDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ContactDatabase.java index 6ca06ed..220f551 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()) { @@ -44,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()) { @@ -94,7 +127,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 +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 79fa09b..008a147 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -1,11 +1,11 @@ package org.to.telegramfinalproject.Database; +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; import java.util.UUID; @@ -76,5 +76,528 @@ 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 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)) { + 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; + } + } + 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(); + } + } + + + + 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; + } + + + 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) { + 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 (SQLException 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; + } + } + + + 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/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 3f959eb..22ef33b 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(); @@ -182,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 3427fa8..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(); @@ -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/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/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/JsonUtil.java b/src/main/java/org/to/telegramfinalproject/Models/JsonUtil.java index 4e3a37f..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()); @@ -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,32 +124,45 @@ 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(); 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); } @@ -154,4 +171,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/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/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/ChannelService.java b/src/main/java/org/to/telegramfinalproject/Server/ChannelService.java new file mode 100644 index 0000000..7901d62 --- /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,"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 230ff53..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,8 @@ 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.*; import java.net.Socket; @@ -69,20 +71,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()); @@ -100,17 +94,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; @@ -123,7 +154,6 @@ public class ClientHandler implements Runnable { } break; } - case "logout": { String user_Id = requestJson.optString("user_id"); if (user_Id != null && !user_Id.isEmpty()) { @@ -133,14 +163,70 @@ public class ClientHandler implements Runnable { 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."); + } 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 "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": { @@ -154,6 +240,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); } @@ -162,6 +249,7 @@ public class ClientHandler implements Runnable { JSONObject obj = new JSONObject(); obj.put("type", "group"); obj.put("id", g.getGroup_id()); + obj.put("uuid", g.getInternal_uuid().toString()); obj.put("name", g.getGroup_name()); results.add(obj); } @@ -170,6 +258,7 @@ public class ClientHandler implements Runnable { JSONObject obj = new JSONObject(); obj.put("type", "channel"); obj.put("id", c.getChannel_id()); + obj.put("uuid", c.getInternal_uuid().toString()); obj.put("name", c.getChannel_name()); results.add(obj); } @@ -181,6 +270,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); } @@ -224,6 +315,882 @@ 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 = 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."); + break; + } + + + boolean joined = GroupDatabase.addMemberToGroup(userUUID, group.getInternal_uuid()); + response = joined + ? new ResponseModel("success", "Joined group.") + : new ResponseModel("error", "Failed to join group."); + break; + } + + case "join_channel": { + 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."); + break; + } + + + boolean joined = ChannelDatabase.addSubscriberToChannel(userUUID, channel.getInternal_uuid()); + response = joined + ? new ResponseModel("success", "Joined channel.") + : new ResponseModel("error", "Failed to join channel."); + break; + } + + + case "get_chat_info": { + try { + String id = requestJson.getString("receiver_id"); + String type = requestJson.getString("receiver_type"); + JSONObject data = new JSONObject(); + + 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 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("description", group.getDescription() != null ? group.getDescription() : ""); + data.put("type", "group"); + data.put("id", group.getGroup_id()); + + 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 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; + } + } + + if (response == null) { + response = new ResponseModel("success", "Chat info fetched", data); + } + + } catch (Exception e) { + response = new ResponseModel("error", "Error fetching chat info: " + e.getMessage()); + } + 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.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"); + 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"); + 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; + 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"); + 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); + + 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"); + 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); + + 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()); + } + break; + } + + case "add_admin_to_channel": { + 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; + } + + 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") || 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")); + 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 "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")); + + //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_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserUUID = UUID.fromString(requestJson.getString("target_user_id")); + + if (!ChannelPermissionUtil.canRemoveAdmins(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to remove admins."); + break; + } + + String targetRole = ChannelDatabase.getChannelRole(channelId, 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 = ChannelDatabase.demoteAdminToSubscriber(channelId, 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 == 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; + } + + 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 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.findByInternalUUID(UUID.fromString(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 "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")); + + 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; + } + + + 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; + } + + + 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: response = new ResponseModel("error", "Unknown action: " + action); } @@ -236,7 +1203,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 +1228,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/GroupService.java b/src/main/java/org/to/telegramfinalproject/Server/GroupService.java new file mode 100644 index 0000000..1d79728 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Server/GroupService.java @@ -0,0 +1,26 @@ +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"); + return true; + } + return false; + } + + + + +} 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/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 new file mode 100644 index 0000000..68e89ba --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Server/RealTimeEventDispatcher.java @@ -0,0 +1,250 @@ +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); + System.out.println("🚀 Sending to user: " + userId + " → " + data); + + 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/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; + } +} 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; + } +} 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