diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index bd914e2..1ec86bd 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -525,6 +525,7 @@ public class ActionHandler { refreshChatList(); System.out.println("✅ Created and opening chat..."); + refreshChatList(); openChat(chat); } @@ -772,42 +773,119 @@ public class ActionHandler { private boolean showChannelChatMenu(ChatEntry chat) { + chat = fetchChatInfo(chat.getId().toString(), chat.getType()); boolean isAdmin = chat.isAdmin(); boolean isOwner = chat.isOwner(); + JSONObject perms = getChannelPermissions(chat.getId()); + System.out.println("\n--- Channel Menu ---"); - if (isOwner || isAdmin) + if (isOwner || (isAdmin && perms.optBoolean("can_post", false))) { System.out.println("1. Send Post"); - - System.out.println("2. View Subscribers"); - - if (isOwner || isAdmin) { - System.out.println("3. Add Admin"); - System.out.println("4. Edit Channel Info"); } - System.out.println("5. Unsubscribe"); + if (isOwner || isAdmin) { + System.out.println("2. View Subscribers"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) { + System.out.println("3. Add Subscriber"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_remove_members", false))) { + System.out.println("4. Remove Subscriber"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_edit_channel", false))) { + System.out.println("5. Edit Channel Info"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_add_admins", false))) { + System.out.println("6. Add Admin"); + } + + if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) { + System.out.println("7. Remove Admin"); + } + + if (isOwner) { + System.out.println("8. Delete Channel"); + System.out.println("9. Leave Channel (Transfer ownership required)"); + } else { + System.out.println("8. Leave Channel"); + } + System.out.println("0. Back to Chat List"); String input = scanner.nextLine(); switch (input) { case "1" -> { - if (isOwner || isAdmin) sendMessageTo(chat.getId(), "channel"); - else System.out.println("❌ You are not allowed to post."); + if (isOwner || (isAdmin && perms.optBoolean("can_post", false))) { + sendMessageTo(chat.getId(), "channel"); + } else { + System.out.println("❌ You don't have permission to post."); + } + } + case "2" -> { + if (isOwner || isAdmin) { + viewChannelSubscribers(chat.getId()); + } else { + System.out.println("❌ You don't have permission to view subscribers."); + } } - case "2" -> viewChannelSubscribers(chat.getId()); case "3" -> { - if (isOwner) addAdminToChannel(chat.getId()); - else System.out.println("❌ Only owner can add admins."); + if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false))) { + searchEligibleUsers("channel", chat.getId()); + } else { + System.out.println("❌ You don't have permission to add subscribers."); + } } case "4" -> { - if (isOwner || isAdmin) editChannelInfo(chat.getId()); - else System.out.println("❌ You don't have permission."); + if (isOwner || (isAdmin && perms.optBoolean("can_remove_members", false))) { + removeSubscriberFromChannel(chat.getId()); + } else { + System.out.println("❌ You don't have permission to remove subscribers."); + } } case "5" -> { - leaveChat(chat.getId(), "channel"); - return false; + if (isOwner || (isAdmin && perms.optBoolean("can_edit_channel", false))) { + editChannelInfo(chat.getId()); + } else { + System.out.println("❌ You don't have permission to edit channel info."); + } + } + case "6" -> { + if (isOwner || (isAdmin && perms.optBoolean("can_add_admins", false))) { + addAdminToChannel(chat.getId()); + } else { + System.out.println("❌ You don't have permission to add admins."); + } + } + case "7" -> { + if (isOwner || (isAdmin && perms.optBoolean("can_remove_admins", false))) { + removeAdminFromChannel(chat.getId()); + } else { + System.out.println("❌ You don't have permission to remove admins."); + } + } + case "8" -> { + if (isOwner) { + deleteChannel(chat.getId()); + return false; + } else { + leaveChat(chat.getId(), "channel"); + return false; + } + } + case "9" -> { + if (isOwner) { + transferChannelOwnershipAndLeave(chat.getId()); + refreshChatList(); + return false; + } else { + System.out.println("❌ You don't have permission."); + } } case "0" -> { return false; @@ -821,6 +899,9 @@ public class ActionHandler { + + + private void transferOwnershipAndLeave(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_admins"); @@ -893,7 +974,6 @@ public class ActionHandler { String profileName = m.getString("profile_name"); String userId = m.getString("user_id"); - // فقط اونر غیرقابل حذف if (role.equals("owner")) continue; eligible.add(m); @@ -934,6 +1014,82 @@ public class ActionHandler { + private void addAdminToChannel(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_subscribers"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch subscribers."); + return; + } + + JSONArray subscribers = res.getJSONObject("data").getJSONArray("subscribers"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Subscribers List ---"); + for (int i = 0; i < subscribers.length(); i++) { + JSONObject s = subscribers.getJSONObject(i); + String role = s.getString("role"); + String profileName = s.getString("profile_name"); + String userId = s.getString("user_id"); + + if (role.equals("subscriber")) { + eligible.add(s); + System.out.printf("%d. %s (%s)\n", eligible.size(), profileName, userId); + } + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No eligible subscribers to promote."); + return; + } + + System.out.print("Select a subscriber to promote to admin: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("internal_uuid"); + + JSONObject permissions = new JSONObject(); + System.out.print("Can post? (true/false): "); + permissions.put("can_post", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can edit channel info? (true/false): "); + permissions.put("can_edit_channel", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add members? (true/false): "); + permissions.put("can_add_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove members? (true/false): "); + permissions.put("can_remove_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add admins? (true/false): "); + permissions.put("can_add_admins", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove admins? (true/false): "); + permissions.put("can_remove_admins", Boolean.parseBoolean(scanner.nextLine())); + + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "add_admin_to_channel"); + promoteReq.put("channel_id", channelId.toString()); + promoteReq.put("target_user_id", targetInternalUUID); + promoteReq.put("permissions", permissions); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes != null) + System.out.println(promoteRes.getString("message")); + } + + + private void addAdminToGroup(UUID groupId) { JSONObject req = new JSONObject(); @@ -1038,6 +1194,142 @@ public class ActionHandler { } + + private void removeSubscriberFromChannel(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_subscribers"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch subscribers."); + return; + } + + JSONArray subscribers = res.getJSONObject("data").getJSONArray("subscribers"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Subscribers List ---"); + for (int i = 0; i < subscribers.length(); i++) { + JSONObject s = subscribers.getJSONObject(i); + String role = s.getString("role"); + String profileName = s.getString("profile_name"); + String userId = s.getString("user_id"); + + if (role.equals("owner")) continue; + + eligible.add(s); + System.out.println((eligible.size()) + ". " + profileName + " (" + userId + ") [" + role + "]"); + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No removable subscribers."); + return; + } + + System.out.print("Select a subscriber to remove: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("internal_uuid"); + + JSONObject removeReq = new JSONObject(); + removeReq.put("action", "remove_subscriber_from_channel"); + removeReq.put("channel_id", channelId.toString()); + removeReq.put("user_id", targetInternalUUID); + + JSONObject removeRes = sendWithResponse(removeReq); + if (removeRes != null) + System.out.println(removeRes.getString("message")); + } + + + private void transferChannelOwnershipAndLeave(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_admins"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch admins."); + return; + } + + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + + if (admins.length() == 0) { + System.out.println("⚠️ No other admins available. You cannot leave without promoting someone to owner."); + return; + } + + System.out.println("\n--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + System.out.printf("%d. %s (%s)\n", i + 1, admin.getString("profile_name"), admin.getString("user_id")); + } + + System.out.print("Select a new owner by number: "); + int choice = Integer.parseInt(scanner.nextLine()) - 1; + + if (choice < 0 || choice >= admins.length()) { + System.out.println("Invalid selection."); + return; + } + + JSONObject selected = admins.getJSONObject(choice); + String newOwnerId = selected.getString("user_id"); + + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "transfer_channel_ownership"); + promoteReq.put("channel_id", channelId.toString()); + promoteReq.put("new_owner_user_id", newOwnerId); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes == null || !promoteRes.getString("status").equals("success")) { + System.out.println("❌ Failed to transfer ownership."); + return; + } + + System.out.println("✅ Ownership transferred successfully."); + leaveChat(channelId, "channel"); + } + + + + private void deleteChannel(UUID channelId) { + System.out.print("Are you sure you want to delete the channel? (yes/no): "); + String confirm = scanner.nextLine().trim().toLowerCase(); + + if (!confirm.equals("yes")) { + System.out.println("❌ Delete cancelled."); + return; + } + + JSONObject req = new JSONObject(); + req.put("action", "delete_channel"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res != null && res.getString("status").equals("success")) { + System.out.println("✅ Channel deleted successfully."); + refreshChatList(); + } else { + System.out.println("❌ Failed to delete channel."); + } + } + + private void deleteGroup(UUID groupId) { System.out.print("Are you sure you want to delete the group? (yes/no): "); String confirm = scanner.nextLine().trim().toLowerCase(); @@ -1118,6 +1410,68 @@ public class ActionHandler { } + private void removeAdminFromChannel(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_admins"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch admins."); + return; + } + + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + List eligible = new ArrayList<>(); + + System.out.println("\n--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + String role = admin.getString("role"); + String profileName = admin.getString("profile_name"); + String userId = admin.getString("user_id"); + + if (!role.equals("owner")) { + eligible.add(admin); + System.out.printf("%d. %s (%s)\n", eligible.size(), profileName, userId); + } + } + + if (eligible.isEmpty()) { + System.out.println("⚠️ No removable admins."); + return; + } + + System.out.print("Select an admin to remove: "); + int choice; + try { + choice = Integer.parseInt(scanner.nextLine()) - 1; + } catch (Exception e) { + System.out.println("❌ Invalid input."); + return; + } + + if (choice < 0 || choice >= eligible.size()) { + System.out.println("❌ Invalid selection."); + return; + } + + JSONObject selected = eligible.get(choice); + String targetInternalUUID = selected.getString("internal_uuid"); + + JSONObject removeReq = new JSONObject(); + removeReq.put("action", "remove_admin_from_channel"); + removeReq.put("channel_id", channelId.toString()); + removeReq.put("target_user_id", targetInternalUUID); + + JSONObject removeRes = sendWithResponse(removeReq); + if (removeRes != null) + System.out.println(removeRes.getString("message")); + } + + + + private void sendMessageTo(UUID id, String type) { System.out.print("Enter message: "); String text = scanner.nextLine().trim(); @@ -1156,15 +1510,19 @@ public class ActionHandler { } } - private void addSubscriberToChannel(UUID channelId, UUID userId) { + private void addSubscriberToChannel(UUID channelId, UUID targetUserId) { JSONObject req = new JSONObject(); req.put("action", "add_subscriber_to_channel"); req.put("channel_id", channelId.toString()); - req.put("user_id", userId.toString()); + req.put("user_id", targetUserId.toString()); JSONObject res = sendWithResponse(req); - if (res != null) { - System.out.println(res.getString("message")); + if (res == null) return; + + if (res.getString("status").equals("success")) { + System.out.println("✅ Subscriber added successfully."); + } else { + System.out.println("❌ " + res.getString("message")); } } @@ -1253,8 +1611,10 @@ public class ActionHandler { JSONObject res = sendWithResponse(req); if (res == null) return; - if (res.getBoolean("success")) { - JSONArray subs = res.getJSONArray("subscribers"); + if (res.getString("status").equals("success")) { + JSONObject data = res.getJSONObject("data"); + JSONArray subs = data.getJSONArray("subscribers"); + System.out.println("--- Subscribers ---"); for (int i = 0; i < subs.length(); i++) { JSONObject s = subs.getJSONObject(i); @@ -1265,36 +1625,81 @@ public class ActionHandler { } } - private void addAdminToChannel(UUID channelId) { - System.out.print("Enter user_id to promote: "); - String userId = scanner.nextLine().trim(); + + + private void editChannelInfo(UUID channelInternalId) { JSONObject req = new JSONObject(); - req.put("action", "add_admin_to_channel"); - req.put("channel_id", channelId.toString()); - req.put("user_id", userId); + req.put("action", "get_chat_info"); + req.put("receiver_id", channelInternalId.toString()); + req.put("receiver_type", "channel"); JSONObject res = sendWithResponse(req); - if (res != null) - System.out.println(res.getString("message")); - } + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch channel info."); + return; + } - private void editChannelInfo(UUID channelId) { - System.out.print("Enter new channel name: "); - String name = scanner.nextLine().trim(); + JSONObject data = res.getJSONObject("data"); - System.out.print("Enter new description: "); - String desc = scanner.nextLine().trim(); + String currentId = data.getString("id"); + String currentName = data.getString("name"); + String currentDesc = data.optString("description", null); + String currentImage = data.optString("image_url", null); - JSONObject req = new JSONObject(); - req.put("action", "edit_channel_info"); - req.put("channel_id", channelId.toString()); - req.put("name", name); - req.put("description", desc); + System.out.println("\n--- Current Channel Info ---"); + System.out.println("1. Channel ID: " + currentId); + System.out.println("2. Name: " + currentName); + System.out.println("3. Description: " + currentDesc); + System.out.println("4. Image URL: " + currentImage); + System.out.println("0. Cancel"); - JSONObject res = sendWithResponse(req); - if (res != null) - System.out.println(res.getString("message")); + System.out.print("Select the field you want to edit (0-4): "); + String choice = scanner.nextLine().trim(); + + String newChannelId = currentId; + String newName = currentName; + String newDesc = currentDesc; + String newImage = currentImage; + + switch (choice) { + case "1" -> { + System.out.print("Enter new Channel ID: "); + newChannelId = scanner.nextLine().trim(); + } + case "2" -> { + System.out.print("Enter new Channel Name: "); + newName = scanner.nextLine().trim(); + } + case "3" -> { + System.out.print("Enter new Description: "); + newDesc = scanner.nextLine().trim(); + } + case "4" -> { + System.out.print("Enter new Image URL: "); + newImage = scanner.nextLine().trim(); + } + case "0" -> { + System.out.println("Cancelled."); + return; + } + default -> { + System.out.println("Invalid choice."); + return; + } + } + + JSONObject editReq = new JSONObject(); + editReq.put("action", "edit_channel_info"); + editReq.put("channel_id", channelInternalId.toString()); // internal_uuid + editReq.put("new_channel_id", newChannelId); + editReq.put("name", newName); + editReq.put("description", newDesc); + editReq.put("image_url", newImage); + + JSONObject editRes = sendWithResponse(editReq); + if (editRes != null) + System.out.println(editRes.getString("message")); } @@ -1395,6 +1800,74 @@ public class ActionHandler { } + private void editChannelAdminPermissions(UUID channelId) { + System.out.print("Enter user_id of the admin to edit: "); + String userId = scanner.nextLine().trim(); + + JSONObject permissions = new JSONObject(); + System.out.print("Can post? (true/false): "); + permissions.put("can_post", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can edit channel info? (true/false): "); + permissions.put("can_edit_channel", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add members? (true/false): "); + permissions.put("can_add_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove members? (true/false): "); + permissions.put("can_remove_members", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can add admins? (true/false): "); + permissions.put("can_add_admins", Boolean.parseBoolean(scanner.nextLine())); + System.out.print("Can remove admins? (true/false): "); + permissions.put("can_remove_admins", Boolean.parseBoolean(scanner.nextLine())); + + JSONObject req = new JSONObject(); + req.put("action", "edit_channel_admin_permissions"); + req.put("channel_id", channelId.toString()); + req.put("user_id", userId); + req.put("permissions", permissions); + + JSONObject res = sendWithResponse(req); + if (res != null) + System.out.println(res.getString("message")); + } + + + private void viewChannelAdmins(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "view_channel_admins"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null) return; + + if (res.getString("status").equals("success")) { + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + + System.out.println("\n--- Channel Admins ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject a = admins.getJSONObject(i); + System.out.printf("- %s (%s) [%s]\n", + a.getString("profile_name"), + a.getString("user_id"), + a.getString("role")); + } + } else { + System.out.println("❌ Failed to fetch admins."); + } + } + + + private JSONObject getChannelPermissions(UUID channelId) { + JSONObject req = new JSONObject(); + req.put("action", "get_channel_permissions"); + req.put("channel_id", channelId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + return new JSONObject(); + } + return res.getJSONObject("data"); + } + + public void editAdminPermissions(String type, UUID entityId) { System.out.print("Enter user ID of the admin to edit: "); String targetUserId = scanner.nextLine().trim(); diff --git a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java index 16ae524..4a32050 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java +++ b/src/main/java/org/to/telegramfinalproject/Client/TelegramClient.java @@ -34,7 +34,6 @@ public class TelegramClient { System.out.println("✅ Connected to Telegram Server"); this.handler = new ActionHandler(this.out, this.in, this.scanner); - // 👂 فقط این ترد مجاز به خواندن از in است Thread listenerThread = new Thread(new IncomingMessageListener(in)); listenerThread.setDaemon(true); listenerThread.start(); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index 668ba34..efa8535 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -1,5 +1,6 @@ package org.to.telegramfinalproject.Database; +import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.Channel; import org.to.telegramfinalproject.Models.Group; @@ -364,22 +365,36 @@ public class ChannelDatabase { public static List getChannelAdminsAndOwner(UUID channelId) { - String sql = "SELECT user_id, role, permissions FROM channel_subscribers WHERE channel_id = ? AND role IN ('owner', 'admin')"; List admins = new ArrayList<>(); + + String sql = """ + SELECT u.internal_uuid, u.profile_name, u.user_id, cs.role, cs.permissions + FROM channel_subscribers cs + JOIN users u ON cs.user_id = u.internal_uuid + WHERE cs.channel_id = ? AND (cs.role = 'owner' OR cs.role = 'admin') + """; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, channelId); - ResultSet rs = stmt.executeQuery(); - while (rs.next()) { - JSONObject obj = new JSONObject(); - obj.put("user_id", rs.getObject("user_id").toString()); - obj.put("role", rs.getString("role")); - obj.put("permissions", new JSONObject(rs.getString("permissions"))); - admins.add(obj); + + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + JSONObject obj = new JSONObject(); + obj.put("internal_uuid", rs.getObject("internal_uuid").toString()); + obj.put("profile_name", rs.getString("profile_name")); + obj.put("user_id", rs.getString("user_id")); + obj.put("role", rs.getString("role")); + obj.put("permissions", new JSONObject(rs.getString("permissions"))); + admins.add(obj); + } } - } catch (Exception e) { + + } catch (SQLException e) { e.printStackTrace(); } + return admins; } @@ -452,5 +467,138 @@ public class ChannelDatabase { return false; } } + public static JSONArray getChannelSubscribers(UUID channelId) { + JSONArray subscribers = new JSONArray(); + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement( + "SELECT u.internal_uuid, u.user_id, u.profile_name, " + + "CASE WHEN cs.role = 'owner' THEN 'owner' " + + " WHEN cs.role = 'admin' THEN 'admin' " + + " ELSE 'subscriber' END AS role " + + "FROM channel_subscribers cs " + + "JOIN users u ON cs.user_id = u.internal_uuid " + + "WHERE cs.channel_id = ?")) { + + stmt.setObject(1, channelId); + ResultSet rs = stmt.executeQuery(); + + while (rs.next()) { + JSONObject obj = new JSONObject(); + obj.put("internal_uuid", rs.getObject("internal_uuid").toString()); + obj.put("user_id", rs.getString("user_id")); + obj.put("profile_name", rs.getString("profile_name")); + obj.put("role", rs.getString("role")); + subscribers.put(obj); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return subscribers; + } + + + + public static boolean updateChannelInfo(UUID channelId, String newId, String name, String description, String imageUrl) { + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement( + "UPDATE channels SET channel_id = ?, channel_name = ?, description = ?, image_url = ? WHERE internal_uuid = ?")) { + + stmt.setString(1, newId); + stmt.setString(2, name); + stmt.setString(3, description); + stmt.setString(4, imageUrl); + stmt.setObject(5, channelId); + + int rows = stmt.executeUpdate(); + return rows > 0; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean isChannelIdUnique(String channelId, UUID excludeChannelUUID) { + String query = "SELECT COUNT(*) FROM channels WHERE channel_id = ? AND internal_uuid != ?"; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(query)) { + + stmt.setString(1, channelId); + stmt.setObject(2, excludeChannelUUID); + + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + int count = rs.getInt(1); + return count == 0; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + public static boolean demoteAdminToSubscriber(UUID channelId, UUID userId) { + String sql = "UPDATE channel_subscribers SET role = 'member', permissions = '{}'::jsonb WHERE channel_id = ? AND user_id = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + + int affected = stmt.executeUpdate(); + return affected > 0; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static boolean deleteChannel(UUID channelId) { + String sql = "DELETE FROM channels WHERE internal_uuid = ?"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, channelId); + int affected = stmt.executeUpdate(); + return affected > 0; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + + public static boolean transferOwnership(UUID channelId, UUID newOwnerUUID) { + String sql = """ + UPDATE channel_subscribers + SET role = CASE + WHEN user_id = ? THEN 'owner' + WHEN role = 'owner' THEN 'admin' + ELSE role + END + WHERE channel_id = ? + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, newOwnerUUID); + stmt.setObject(2, channelId); + + stmt.executeUpdate(); + return true; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } } diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index f3bf40b..76022b6 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -4,6 +4,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Database.*; import org.to.telegramfinalproject.Models.*; +import org.to.telegramfinalproject.Utils.ChannelPermissionUtil; import org.to.telegramfinalproject.Utils.GroupPermissionUtil; import java.io.*; @@ -411,19 +412,27 @@ public class ClientHandler implements Runnable { case "channel" -> { - Channel channel = ChannelDatabase.findByChannelId(id); + Channel channel = ChannelDatabase.findByInternalUUID(UUID.fromString(id)); if (channel != null) { data.put("internal_id", channel.getInternal_uuid().toString()); data.put("name", channel.getChannel_name()); data.put("image_url", channel.getImage_url()); + data.put("description", channel.getDescription() != null ? channel.getDescription() : ""); data.put("type", "channel"); data.put("id", channel.getChannel_id()); + + boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), currentUser.getInternal_uuid()); + boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), currentUser.getInternal_uuid()); + + data.put("is_owner", isOwner); + data.put("is_admin", isAdmin); } else { response = new ResponseModel("error", "Channel not found."); break; } } + default -> { response = new ResponseModel("error", "Unknown type."); break; @@ -588,27 +597,38 @@ public class ClientHandler implements Runnable { break; } - case "add_admin_to_channel": { - UUID channelId = UUID.fromString(requestJson.getString("channel_id")); - UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); - JSONObject permissions = requestJson.optJSONObject("permissions"); + try { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); + JSONObject permissions = requestJson.optJSONObject("permissions"); - //if (!ChannelPermissionUtil.canAddAdmins(channelId, currentUser.getInternal_uuid())) { - //response = new ResponseModel("error", "You are not allowed to add admins to the channel."); - //break; - //} + if (!ChannelPermissionUtil.canAddAdmins(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to add admins to the channel."); + break; + } + String targetRole = ChannelDatabase.getChannelRole(channelId, targetUserId); + if (targetRole == null) { + response = new ResponseModel("error", "User is not a subscriber of the channel."); + break; + } - boolean success = ChannelDatabase.addAdminToChannel(channelId, targetUserId, permissions); - response = success - ? new ResponseModel("success", "Admin added to channel.") - : new ResponseModel("error", "Failed to add admin."); + if (targetRole.equals("owner") || targetRole.equals("admin")) { + response = new ResponseModel("error", "User is already an admin or owner."); + break; + } + + boolean success = ChannelDatabase.addAdminToChannel(channelId, targetUserId, permissions); + response = success + ? new ResponseModel("success", "Admin added to channel.") + : new ResponseModel("error", "Failed to add admin."); + } catch (Exception e) { + response = new ResponseModel("error", "Error adding admin to channel: " + e.getMessage()); + } break; } - - case "edit_channel_admin_permissions": { UUID channelId = UUID.fromString(requestJson.getString("channel_id")); UUID targetUserId = UUID.fromString(requestJson.getString("target_user_id")); @@ -724,25 +744,16 @@ public class ClientHandler implements Runnable { - case "remove_admin_from_group": { - UUID groupId = UUID.fromString(requestJson.getString("group_id")); - String targetUserIdStr = requestJson.getString("user_id"); + case "remove_admin_from_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserUUID = UUID.fromString(requestJson.getString("target_user_id")); - - User targetUser = new userDatabase().findByUserId(targetUserIdStr); - if (targetUser == null) { - response = new ResponseModel("error", "User not found."); - break; - } - - UUID targetUserUUID = targetUser.getInternal_uuid(); - - if (!GroupPermissionUtil.canRemoveAdmins(groupId, currentUser.getInternal_uuid())) { + if (!ChannelPermissionUtil.canRemoveAdmins(channelId, currentUser.getInternal_uuid())) { response = new ResponseModel("error", "You are not allowed to remove admins."); break; } - String targetRole = GroupDatabase.getGroupRole(groupId, targetUserUUID); + String targetRole = ChannelDatabase.getChannelRole(channelId, targetUserUUID); if (targetRole.equals("owner")) { response = new ResponseModel("error", "You cannot remove the owner."); break; @@ -752,7 +763,7 @@ public class ClientHandler implements Runnable { break; } - boolean success = GroupDatabase.demoteAdminToMember(groupId, targetUserUUID); + boolean success = ChannelDatabase.demoteAdminToSubscriber(channelId, targetUserUUID); response = success ? new ResponseModel("success", "Admin removed successfully.") : new ResponseModel("error", "Failed to remove admin."); @@ -760,7 +771,6 @@ public class ClientHandler implements Runnable { } - case "add_member_to_group": { UUID groupId = UUID.fromString(requestJson.getString("group_id")); UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); @@ -856,7 +866,7 @@ public class ClientHandler implements Runnable { case "channel" -> { - Channel channel = ChannelDatabase.findByChannelId(receiverId); + Channel channel = ChannelDatabase.findByInternalUUID(UUID.fromString(receiverId)); if (channel == null) { response = new ResponseModel("error", "Channel not found."); break; @@ -1021,6 +1031,164 @@ public class ClientHandler implements Runnable { } + case "get_channel_permissions": { + try { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + userId = currentUser.getInternal_uuid(); + + JSONObject permissions = ChannelDatabase.getChannelPermissions(channelId, userId); + + response = new ResponseModel("success", "Permissions fetched.", permissions); + } catch (Exception e) { + response = new ResponseModel("error", "Error fetching channel permissions: " + e.getMessage()); + } + break; + } + + + case "view_channel_subscribers": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + + boolean isOwner = ChannelDatabase.isOwner(channelId, currentUser.getInternal_uuid()); + boolean isAdmin = ChannelDatabase.isAdmin(channelId, currentUser.getInternal_uuid()); + + if (!isOwner && !isAdmin) { + response = new ResponseModel("error", "You are not authorized to view subscribers."); + break; + } + + JSONArray subscribers = ChannelDatabase.getChannelSubscribers(channelId); + + if (subscribers != null) { + JSONObject data = new JSONObject(); + data.put("subscribers", subscribers); + response = new ResponseModel("success", "Subscribers fetched successfully.", data); + } else { + response = new ResponseModel("error", "Failed to fetch subscribers."); + } + break; + } + + + case "add_subscriber_to_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); + + if (!ChannelPermissionUtil.canAddSubscribers(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to add subscribers."); + break; + } + + if (ChannelDatabase.isUserInChannel(targetUserId, channelId)) { + response = new ResponseModel("error", "User is already a subscriber."); + break; + } + + boolean success = ChannelDatabase.addSubscriberToChannel(targetUserId, channelId); + response = success + ? new ResponseModel("success", "Subscriber added to channel.") + : new ResponseModel("error", "Failed to add subscriber."); + break; + } + + case "remove_subscriber_from_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + UUID targetUserId = UUID.fromString(requestJson.getString("user_id")); + + if (!ChannelPermissionUtil.canRemoveSubscribers(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You are not allowed to remove subscribers."); + break; + } + + String targetRole = ChannelDatabase.getChannelRole(channelId, targetUserId); + if (targetRole == null) { + response = new ResponseModel("error", "User is not a subscriber of the channel."); + break; + } + + if (targetRole.equals("owner")) { + response = new ResponseModel("error", "You cannot remove the owner."); + break; + } + + boolean success = ChannelDatabase.removeSubscriberFromChannel(channelId, targetUserId); + response = success + ? new ResponseModel("success", "Subscriber removed from channel.") + : new ResponseModel("error", "Failed to remove subscriber."); + break; + } + + + case "edit_channel_info": { + try { + UUID channelUUID = UUID.fromString(requestJson.getString("channel_id")); // internal_uuid + String newChannelId = requestJson.getString("new_channel_id").trim(); + String name = requestJson.optString("name"); + String description = requestJson.optString("description", null); + String imageUrl = requestJson.has("image_url") && !requestJson.isNull("image_url") + ? requestJson.getString("image_url") : null; + + boolean isOwner = ChannelDatabase.isOwner(channelUUID, currentUser.getInternal_uuid()); + if (!isOwner && !ChannelPermissionUtil.canEditChannel(channelUUID, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "You don't have permission to edit this channel."); + break; + } + + if (isOwner && !ChannelDatabase.isChannelIdUnique(newChannelId, channelUUID)) { + response = new ResponseModel("error", "Channel ID is already taken by another channel."); + break; + } + + boolean updated = ChannelDatabase.updateChannelInfo(channelUUID, newChannelId, name, description, imageUrl); + response = updated + ? new ResponseModel("success", "Channel info updated successfully.") + : new ResponseModel("error", "Failed to update channel info."); + + } catch (Exception e) { + response = new ResponseModel("error", "Error updating channel: " + e.getMessage()); + } + break; + } + + + case "delete_channel": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + + if (!ChannelDatabase.isOwner(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "Only the owner can delete the channel."); + break; + } + + boolean success = ChannelDatabase.deleteChannel(channelId); + response = success + ? new ResponseModel("success", "Channel deleted successfully.") + : new ResponseModel("error", "Failed to delete channel."); + + break; + } + + case "transfer_channel_ownership": { + UUID channelId = UUID.fromString(requestJson.getString("channel_id")); + String newOwnerUserIdStr = requestJson.getString("new_owner_user_id"); + + User newOwner = new userDatabase().findByUserId(newOwnerUserIdStr); + if (newOwner == null) { + response = new ResponseModel("error", "User not found."); + break; + } + + if (!ChannelDatabase.isOwner(channelId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "Only the owner can transfer ownership."); + break; + } + + boolean success = ChannelDatabase.transferOwnership(channelId, newOwner.getInternal_uuid()); + response = success + ? new ResponseModel("success", "Ownership transferred successfully.") + : new ResponseModel("error", "Failed to transfer ownership."); + break; + } + default: diff --git a/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java b/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java new file mode 100644 index 0000000..fbdc5ad --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Utils/ChannelPermissionUtil.java @@ -0,0 +1,54 @@ +package org.to.telegramfinalproject.Utils; + +import org.json.JSONObject; +import org.to.telegramfinalproject.Database.ChannelDatabase; + +import java.util.UUID; + +public class ChannelPermissionUtil { + + public static boolean canAddSubscribers(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_add_members", false); + } + return false; + } + + public static boolean canAddAdmins(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_add_admins", false); + } + return false; + } + + public static boolean canEditChannel(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_edit_channel", false); + } + return false; + } + + public static boolean canRemoveAdmins(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_remove_admins", false); + } + return false; + } + + public static boolean canRemoveSubscribers(UUID channelId, UUID userId) { + if (ChannelDatabase.isOwner(channelId, userId)) return true; + if (ChannelDatabase.isAdmin(channelId, userId)) { + JSONObject perms = ChannelDatabase.getChannelPermissions(channelId, userId); + return perms.optBoolean("can_remove_members", false); + } + return false; + } +}