diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 7980f72..bd914e2 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -713,8 +713,11 @@ public class ActionHandler { } if (isOwner) { System.out.println("8. Delete Group"); + System.out.println("9. Leave Group (Transfer ownership required)"); + } else { + System.out.println("9. Leave Group"); } - System.out.println("9. Leave Group"); + System.out.println("0. Back to Chat List"); String input = scanner.nextLine(); @@ -747,9 +750,16 @@ public class ActionHandler { return false; } case "9" -> { - leaveChat(chat.getId(), "group"); + if (isOwner) { + transferOwnershipAndLeave(chat.getId()); + refreshChatList(); + } else { + leaveChat(chat.getId(), "group"); + refreshChatList(); + } return false; } + case "0" -> { return false; } @@ -811,6 +821,57 @@ public class ActionHandler { + private void transferOwnershipAndLeave(UUID groupId) { + JSONObject req = new JSONObject(); + req.put("action", "view_group_admins"); + req.put("group_id", groupId.toString()); + + JSONObject res = sendWithResponse(req); + if (res == null || !res.getString("status").equals("success")) { + System.out.println("❌ Failed to fetch admins."); + return; + } + + JSONArray admins = res.getJSONObject("data").getJSONArray("admins"); + + if (admins.length() == 0) { + System.out.println("⚠️ No other admins available. You cannot leave without promoting someone to owner."); + return; + } + + System.out.println("--- Admins List ---"); + for (int i = 0; i < admins.length(); i++) { + JSONObject admin = admins.getJSONObject(i); + System.out.printf("%d. %s (%s)\n", i + 1, admin.getString("profile_name"), admin.getString("user_id")); + } + + System.out.print("Select a new owner by number: "); + int choice = Integer.parseInt(scanner.nextLine()) - 1; + + if (choice < 0 || choice >= admins.length()) { + System.out.println("Invalid selection."); + return; + } + + JSONObject selected = admins.getJSONObject(choice); + String newOwnerId = selected.getString("user_id"); + + JSONObject promoteReq = new JSONObject(); + promoteReq.put("action", "transfer_group_ownership"); + promoteReq.put("group_id", groupId.toString()); + promoteReq.put("new_owner_user_id", newOwnerId); + + JSONObject promoteRes = sendWithResponse(promoteReq); + if (promoteRes == null || !promoteRes.getString("status").equals("success")) { + System.out.println("❌ Failed to transfer ownership."); + return; + } + + System.out.println("✅ Ownership transferred successfully."); + leaveChat(groupId, "group"); + } + + private void removeMemberFromGroup(UUID groupId) { JSONObject req = new JSONObject(); req.put("action", "view_group_members"); @@ -1046,13 +1107,17 @@ public class ActionHandler { JSONObject res = sendWithResponse(req); if (res == null) return; - if (res.getBoolean("success")) { + String status = res.getString("status"); + String message = res.getString("message"); + + if (status.equals("success")) { System.out.println("✅ You left the chat."); } else { - System.out.println("❌ " + res.getString("message")); + System.out.println("❌ " + message); } } + private void sendMessageTo(UUID id, String type) { System.out.print("Enter message: "); String text = scanner.nextLine().trim(); @@ -1120,8 +1185,8 @@ public class ActionHandler { String currentId = data.getString("id"); String currentName = data.getString("name"); - String currentDesc = data.optString("description", "None"); - String currentImage = data.optString("image_url", "None"); + String currentDesc = data.optString("description", null); + String currentImage = data.optString("image_url", null); System.out.println("\n--- Current Group Info ---"); System.out.println("1. Group ID: " + currentId); diff --git a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java index f293efa..668ba34 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ChannelDatabase.java @@ -435,5 +435,22 @@ public class ChannelDatabase { } + public static boolean removeSubscriberFromChannel(UUID channelId, UUID userId) { + String sql = "DELETE FROM channel_subscribers WHERE channel_id = ? AND user_id = ?"; + + try (Connection conn =ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setObject(1, channelId); + stmt.setObject(2, userId); + + int affectedRows = stmt.executeUpdate(); + return affectedRows > 0; + + } catch (SQLException e) { + System.err.println("Error removing subscriber from channel: " + e.getMessage()); + return false; + } + } } diff --git a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java index 1eb550b..008a147 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/GroupDatabase.java @@ -404,22 +404,32 @@ public class GroupDatabase { public static List getGroupAdminsAndOwner(UUID groupId) { - String sql = "SELECT user_id, role, permissions FROM group_members WHERE group_id = ? AND role IN ('owner', 'admin')"; List admins = new ArrayList<>(); + + String sql = "SELECT gm.user_id, gm.role, gm.permissions, u.profile_name " + + "FROM group_members gm " + + "JOIN users u ON gm.user_id = u.internal_uuid " + + "WHERE gm.group_id = ? AND gm.role IN ('owner', 'admin')"; + try (Connection conn = ConnectionDb.connect(); PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, groupId); ResultSet rs = stmt.executeQuery(); + while (rs.next()) { JSONObject obj = new JSONObject(); obj.put("user_id", rs.getObject("user_id").toString()); obj.put("role", rs.getString("role")); obj.put("permissions", new JSONObject(rs.getString("permissions"))); + obj.put("profile_name", rs.getString("profile_name")); admins.add(obj); } - } catch (Exception e) { + + } catch (SQLException e) { e.printStackTrace(); } + return admins; } @@ -544,4 +554,50 @@ public class GroupDatabase { } + public static boolean transferOwnership(UUID groupId, UUID newOwnerId) { + String demoteOldOwner = "UPDATE group_members SET role = 'admin' WHERE group_id = ? AND role = 'owner'"; + String promoteNewOwner = "UPDATE group_members SET role = 'owner' WHERE group_id = ? AND user_id = ?"; + + try (Connection conn = ConnectionDb.connect()) { + conn.setAutoCommit(false); + + try (PreparedStatement demoteStmt = conn.prepareStatement(demoteOldOwner); + PreparedStatement promoteStmt = conn.prepareStatement(promoteNewOwner)) { + + demoteStmt.setObject(1, groupId); + demoteStmt.executeUpdate(); + + promoteStmt.setObject(1, groupId); + promoteStmt.setObject(2, newOwnerId); + promoteStmt.executeUpdate(); + + conn.commit(); + return true; + } catch (SQLException e) { + conn.rollback(); + e.printStackTrace(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + + public static boolean deleteGroup(UUID groupId) { + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement("DELETE FROM groups WHERE internal_uuid = ?")) { + + stmt.setObject(1, groupId); + int affectedRows = stmt.executeUpdate(); + + return affectedRows > 0; + + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + } \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 4f64bae..f3bf40b 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -912,6 +912,36 @@ public class ClientHandler implements Runnable { + case "transfer_group_ownership": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + UUID newOwnerUserId = UUID.fromString(requestJson.getString("new_owner_user_id")); + + User newOwner = userDatabase.findByInternalUUID(newOwnerUserId); + if (newOwner == null) { + response = new ResponseModel("error", "New owner not found."); + break; + } + + if (!GroupDatabase.isOwner(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "Only current owner can transfer ownership."); + break; + } + + if (!GroupDatabase.isAdmin(groupId, newOwner.getInternal_uuid())) { + response = new ResponseModel("error", "Selected user is not an admin."); + break; + } + + boolean success = GroupDatabase.transferOwnership(groupId, newOwner.getInternal_uuid()); + + response = success + ? new ResponseModel("success", "Ownership transferred.") + : new ResponseModel("error", "Failed to transfer ownership."); + + break; + } + + case "view_group_members" : { UUID groupId = UUID.fromString(requestJson.getString("group_id")); @@ -948,7 +978,47 @@ public class ClientHandler implements Runnable { } + case "leave_chat": { + String chatType = requestJson.getString("chat_type"); + UUID chatId = UUID.fromString(requestJson.getString("chat_id")); + userId = UUID.fromString(requestJson.getString("user_id")); + boolean success = false; + + switch (chatType) { + case "group" -> success = GroupDatabase.removeMemberFromGroup(chatId, userId); + case "channel" -> success = ChannelDatabase.removeSubscriberFromChannel(chatId, userId); + default -> { + response = new ResponseModel("error", "Unsupported chat type."); + break; + } + } + + if (response == null) { + response = success + ? new ResponseModel("success", "Left the " + chatType + " successfully.") + : new ResponseModel("error", "Failed to leave the " + chatType + "."); + } + + break; + } + + + case "delete_group": { + UUID groupId = UUID.fromString(requestJson.getString("group_id")); + + if (!GroupDatabase.isOwner(groupId, currentUser.getInternal_uuid())) { + response = new ResponseModel("error", "Only the group owner can delete the group."); + break; + } + + boolean success = GroupDatabase.deleteGroup(groupId); + + response = success + ? new ResponseModel("success", "Group deleted successfully.") + : new ResponseModel("error", "Failed to delete group."); + break; + }