Merge pull request #7 from PartowRoshani/Real-Time-update

Real time update
This commit is contained in:
Mohammad Sajjad Zanganeh
2025-07-24 22:23:45 +03:30
committed by GitHub
16 changed files with 2449 additions and 375 deletions
File diff suppressed because it is too large Load Diff
@@ -1,20 +1,38 @@
package org.to.telegramfinalproject.Client; package org.to.telegramfinalproject.Client;
public class EventProcessorThread extends Thread { import org.json.JSONObject;
private final ActionHandler handler;
public EventProcessorThread(ActionHandler handler) { import java.io.BufferedReader;
this.handler = handler;
setDaemon(true);
}
@Override //public class EventProcessorThread extends Thread {
public void run() { // private final ActionHandler handler;
while (true) { // private final BufferedReader in;
try { //
Thread.sleep(2000); // public EventProcessorThread(ActionHandler handler, BufferedReader in) {
handler.processIncomingEvents(); // this.handler = handler;
} catch (InterruptedException ignored) {} // this.in = in;
} // setDaemon(true);
} // }
} //
// @Override
// public void run() {
// try {
// System.out.println("👂 Real-Time Listener started.");
// String line;
// while ((line = in.readLine()) != null) {
// JSONObject json = new JSONObject(line);
// System.out.println("📥 Received raw line: " + line);
//
// if (json.has("action")) {
// // پیام real-time
// handler.processIncomingEvent(json);
// } else {
// // پیام پاسخ معمولی
// TelegramClient.responseQueue.put(json);
// }
// }
// } catch (Exception e) {
// System.err.println("❌ Error in EventProcessorThread: " + e.getMessage());
// }
// }
//}
@@ -1,9 +1,13 @@
package org.to.telegramfinalproject.Client; package org.to.telegramfinalproject.Client;
import org.json.JSONObject; import org.json.JSONObject;
import org.to.telegramfinalproject.Models.ChatEntry;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
public class IncomingMessageListener implements Runnable { public class IncomingMessageListener implements Runnable {
private final BufferedReader in; private final BufferedReader in;
@@ -19,25 +23,52 @@ public class IncomingMessageListener implements Runnable {
String line; String line;
while ((line = in.readLine()) != null) { while ((line = in.readLine()) != null) {
JSONObject response = new JSONObject(line); JSONObject response = new JSONObject(line);
System.out.println("📥 Received raw line: " + line); System.out.println("📥 Received raw line: " + line);
//if it has reqID answer
if (response.has("request_id")) {
String requestId = response.getString("request_id");
System.out.println("📬 Response with request_id: " + requestId);
System.out.println("📬 Full response: " + response.toString(2));
BlockingQueue<JSONObject> queue = TelegramClient.pendingResponses.get(requestId);
if (queue != null) {
queue.put(response);
} else {
System.out.println("⚠️ No pending queue for request_id = " + requestId + ". Putting in responseQueue...");
TelegramClient.responseQueue.put(response);
}
continue;
}
//if it has action check it
if (response.has("action")) { if (response.has("action")) {
String action = response.getString("action"); String action = response.getString("action");
System.out.println("🎯 [Listener] Action received: " + response.toString(2));
System.out.println("🎯 Received action: " + action);
if (isRealTimeEvent(action)) { if (isRealTimeEvent(action)) {
handleRealTimeEvent(response); handleRealTimeEvent(response);
} else { } else {
TelegramClient.responseQueue.put(response); TelegramClient.responseQueue.put(response);
} }
} else if (response.has("status") && response.has("message")) { } else if (response.has("status") && response.has("message")) {
TelegramClient.responseQueue.put(response); TelegramClient.responseQueue.put(response); // general answer
} else { } else {
TelegramClient.responseQueue.put(response); TelegramClient.responseQueue.put(response); // fallback
} }
} }
} catch (Exception e) { } catch (Exception e) {
System.out.println("🔴 Listener stopped: " + e.getMessage()); System.out.println("🔴 [Listener] Crashed due to: " + e.getMessage());
e.printStackTrace();
} }
} }
@@ -46,15 +77,157 @@ public class IncomingMessageListener implements Runnable {
case "new_message", "message_edited", "message_deleted", case "new_message", "message_edited", "message_deleted",
"user_status_changed", "added_to_group", "added_to_channel", "user_status_changed", "added_to_group", "added_to_channel",
"update_group_or_channel", "chat_deleted", "update_group_or_channel", "chat_deleted",
"blocked_by_user", "unblocked_by_user", "message_seen" -> true; "blocked_by_user", "unblocked_by_user", "message_seen",
"removed_from_group", "removed_from_channel",
"became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> true;
default -> false; default -> false;
}; };
} }
private void handleRealTimeEvent(JSONObject response) { void handleRealTimeEvent(JSONObject response) throws IOException {
String action = response.getString("action"); String action = response.getString("action");
JSONObject msg = response.getJSONObject("data"); JSONObject msg = response.getJSONObject("data");
switch (action) {
case "added_to_group", "added_to_channel",
"removed_from_group", "removed_from_channel", "chat_deleted" -> {
System.out.println("🔄 Chat list changed. Updating...");
Session.forceRefreshChatList = true;
System.out.println("🧪 Calling requestChatList() after being added");
String chatId = msg.getString("chat_id");
String chatType = msg.getString("chat_type");
ActionHandler.requestChatInfo(chatId, chatType);
if (action.equals("removed_from_group") || action.equals("removed_from_channel") || action.equals("chat_deleted")) {
System.out.println("🚫 You were removed from the chat or chat was deleted. Exiting...");
ActionHandler.forceExitChat = true;
}
}
case "chat_updated" -> {
System.out.println("\n🔄 Group/Channel info updated.");
new Thread(() -> {
try {
handleAdminRoleChanged(msg);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
case "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> {
System.out.println("🧩 Detected admin/owner role change. Calling handler...");
new Thread(() -> {
try {
handleAdminRoleChanged(msg); //new thread
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
//
default -> displayRealTimeMessage(action, msg);
}
System.out.print(">> ");
}
private void handleAdminRoleChanged(JSONObject data) throws IOException {
String chatType = data.getString("chat_type");
String chatId = data.optString("group_id", data.optString("channel_id", data.optString("chat_id", null)));
if (chatId == null) {
System.out.println("⚠️ No valid ID found in real-time data: " + data.toString(2));
return;
}
System.out.println("\n🔄 Your admin status changed. Updating chat info...");
try {
// 1. get chat info
JSONObject chatInfoReq = new JSONObject();
chatInfoReq.put("action", "get_chat_info");
chatInfoReq.put("receiver_id", chatId);
chatInfoReq.put("receiver_type", chatType);
System.out.println("📤 Sending get_chat_info: " + chatInfoReq);
JSONObject chatInfoResp = ActionHandler.sendWithResponse(chatInfoReq);
JSONObject chatData = chatInfoResp.getJSONObject("data");
UUID chatUUID = UUID.fromString(chatData.getString("internal_id"));
Optional<ChatEntry> entry = Session.chatList.stream()
.filter(e -> e.getId().equals(chatUUID))
.findFirst();
if (entry.isEmpty()) {
System.out.println("❌ Chat not found in session.");
return;
}
entry.ifPresent(chat -> {
chat.setAdmin(chatData.optBoolean("is_admin", false));
chat.setOwner(chatData.optBoolean("is_owner", false));
chat.setName(chatData.optString("name", ""));
chat.setDisplayId(chatData.optString("id", ""));
chat.setImageUrl(chatData.optString("image_url", ""));
chat.setType(chatData.optString("type", ""));
Session.currentChatEntry = chat;
});
// 2. get permission
JSONObject permissionReq = new JSONObject();
if (chatType.equalsIgnoreCase("group")) {
permissionReq.put("action", "get_group_permissions");
permissionReq.put("group_id", chatId);
} else {
permissionReq.put("action", "get_channel_permissions");
permissionReq.put("channel_id", chatId);
}
JSONObject permissionResp = ActionHandler.sendWithResponse(permissionReq);
JSONObject perm = permissionResp.getJSONObject("data");
entry.ifPresent(chat -> chat.setPermissions(perm));
// 3. set currentChatId
Session.currentChatId = chatUUID.toString();
System.out.println("🧪 Checking refresh conditions...");
System.out.println("🔹 inChatMenu: " + Session.inChatMenu);
System.out.println("🔹 currentChatId: " + Session.currentChatId);
System.out.println("🔹 chatUUID: " + chatUUID);
if (Session.inChatMenu && Session.currentChatId != null && Session.currentChatId.equals(chatUUID.toString())) {
synchronized (Session.class) {
Session.refreshCurrentChatMenu = true;
}
System.out.println("✅ Admin status updated. Refreshing menu...");
} else {
System.out.println("❌ Refresh conditions not met.");
}
} catch (Exception e) {
System.out.println("❌ Exception while handling admin role change: " + e.getMessage());
e.printStackTrace();
}
}
private void displayRealTimeMessage(String action, JSONObject msg) {
switch (action) { switch (action) {
case "new_message" -> { case "new_message" -> {
System.out.println("\n🔔 New Message:"); System.out.println("\n🔔 New Message:");
@@ -62,60 +235,36 @@ public class IncomingMessageListener implements Runnable {
System.out.println("Time: " + msg.getString("time")); System.out.println("Time: " + msg.getString("time"));
System.out.println("Content: " + msg.getString("content")); System.out.println("Content: " + msg.getString("content"));
} }
case "message_edited" -> { case "message_edited" -> {
System.out.println("\n✏️ Message Edited:"); System.out.println("\n✏️ Message Edited:");
System.out.println("ID: " + msg.getString("message_id")); System.out.println("ID: " + msg.getString("message_id"));
System.out.println("New Content: " + msg.getString("new_content")); System.out.println("New Content: " + msg.getString("new_content"));
System.out.println("Edit Time: " + msg.getString("edited_at")); System.out.println("Edit Time: " + msg.getString("edited_at"));
} }
case "message_deleted" -> { case "message_deleted" -> {
System.out.println("\n🗑️ Message Deleted:"); System.out.println("\n🗑️ Message Deleted:");
System.out.println("Message ID: " + msg.getString("message_id")); System.out.println("Message ID: " + msg.getString("message_id"));
} }
case "user_status_changed" -> { case "user_status_changed" -> {
System.out.println("\n🔄 User Status Changed:"); System.out.println("\n🔄 User Status Changed:");
System.out.println("User: " + msg.getString("user_id")); System.out.println("User: " + msg.getString("user_id"));
System.out.println("Status: " + msg.getString("status")); 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" -> { case "blocked_by_user" -> {
System.out.println("\n⛔ You were blocked by user: " + msg.getString("blocker_id")); System.out.println("\n⛔ You were blocked by user: " + msg.getString("blocker_id"));
} }
case "unblocked_by_user" -> { case "unblocked_by_user" -> {
System.out.println("\n✅ You were unblocked by user: " + msg.getString("unblocker_id")); System.out.println("\n✅ You were unblocked by user: " + msg.getString("unblocker_id"));
} }
case "message_seen" -> { case "message_seen" -> {
System.out.println("\n👁️ Your message was seen:"); System.out.println("\n👁️ Your message was seen:");
System.out.println("Message ID: " + msg.getString("message_id")); System.out.println("Message ID: " + msg.getString("message_id"));
System.out.println("Seen at: " + msg.getString("seen_at")); System.out.println("Seen at: " + msg.getString("seen_at"));
} }
default -> { default -> {
System.out.println("\n❓ Unknown real-time action: " + action); System.out.println("\n❓ Unknown real-time action: " + action);
System.out.println(msg.toString(2));
} }
} }
System.out.print(">> ");
} }
} }
@@ -1,15 +1,30 @@
package org.to.telegramfinalproject.Client; package org.to.telegramfinalproject.Client;
import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.to.telegramfinalproject.Models.ChatEntry; import org.to.telegramfinalproject.Models.ChatEntry;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
// method for save data from server response // method for save data from server response
public class Session { public class Session {
public static JSONObject currentUser; public static JSONObject currentUser;
public static List<ChatEntry> chatList; public static List<ChatEntry> chatList = new ArrayList<>();
public static volatile boolean forceRefreshChatList = false;
public static volatile boolean backToChatList = false;
public static boolean inChatListMenu = false;
public static String currentChatType = null;
public static volatile boolean inChatMenu = false;
public static volatile boolean refreshCurrentChatMenu = false;
public static String currentChatId = null;
public static ChatEntry currentChatEntry = null;
public static String getUserUUID() { public static String getUserUUID() {
if (currentUser.has("uuid")) return currentUser.getString("uuid"); if (currentUser.has("uuid")) return currentUser.getString("uuid");
@@ -18,4 +33,26 @@ public class Session {
throw new RuntimeException("❌ No UUID found in currentUser!"); throw new RuntimeException("❌ No UUID found in currentUser!");
} }
public static void updateChatList(JSONArray chatArray) {
chatList.clear();
for (int i = 0; i < chatArray.length(); i++) {
JSONObject obj = chatArray.getJSONObject(i);
ChatEntry entry = new ChatEntry(
UUID.fromString(obj.getString("internal_id")),
obj.optString("id", ""), // displayId
obj.optString("name", ""), // name
obj.optString("image_url", ""),
obj.getString("type"),
null, // last message time (if needed, parse it)
obj.optBoolean("is_owner", false),
obj.optBoolean("is_admin", false)
);
entry.setPermissions(obj.optJSONObject("permissions")); // اگر permissions وجود داره
chatList.add(entry);
}
}
public static List<ChatEntry> getChatList() {
return chatList;
}
} }
@@ -7,32 +7,44 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.Socket; import java.net.Socket;
import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
public class TelegramClient { public class TelegramClient {
private static final String SERVER_HOST = "localhost"; private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 8000; private static final int SERVER_PORT = 8000;
private Socket socket; private static Socket socket;
private BufferedReader in; private BufferedReader in;
private PrintWriter out; private PrintWriter out;
private final Scanner scanner; private final Scanner scanner;
ActionHandler handler = null; private ActionHandler handler;
public static BlockingQueue<JSONObject> responseQueue = new LinkedBlockingQueue<>(); public static BlockingQueue<JSONObject> responseQueue = new LinkedBlockingQueue<>();
public static UUID loggedInUserId = null;
public static final Map<String, BlockingQueue<JSONObject>> pendingResponses = new ConcurrentHashMap<>();
private static TelegramClient instance;
public TelegramClient() { public TelegramClient() {
this.scanner = new Scanner(System.in); this.scanner = new Scanner(System.in);
instance = this;
}
public static TelegramClient getInstance() {
return instance;
} }
public void start() { public void start() {
try { try {
this.socket = new Socket(SERVER_HOST, SERVER_PORT); socket = new Socket(SERVER_HOST, SERVER_PORT);
this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.out = new PrintWriter(this.socket.getOutputStream(), true); out = new PrintWriter(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); handler = new ActionHandler(out, in, scanner);
Thread listenerThread = new Thread(new IncomingMessageListener(in)); Thread listenerThread = new Thread(new IncomingMessageListener(in));
listenerThread.setDaemon(true); listenerThread.setDaemon(true);
@@ -45,7 +57,7 @@ public class TelegramClient {
} }
} }
private void showMainMenu() { private void showMainMenu() throws IOException {
while (true) { while (true) {
System.out.println("Main Menu:"); System.out.println("Main Menu:");
System.out.println("1. Register"); System.out.println("1. Register");
@@ -60,7 +72,13 @@ public class TelegramClient {
handler.loginHandler(); handler.loginHandler();
if (Session.currentUser != null) { if (Session.currentUser != null) {
System.out.println("✅ Login successful."); System.out.println("✅ Login successful.");
new Thread(new ActionHandler.ChatStateMonitor(out)).start();
// new Thread(new ActionHandler.CurrentChatMenuRefresher(this.handler)).start();
UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid")); UUID internalId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
loggedInUserId = internalId;
handler.userMenu(internalId); handler.userMenu(internalId);
} else { } else {
System.out.println("❌ Login failed."); System.out.println("❌ Login failed.");
@@ -75,7 +93,30 @@ public class TelegramClient {
} }
} }
public static void send(JSONObject req) {
try {
responseQueue.clear(); // optional: clear old responses
getInstance().out.println(req.toString());
System.out.println("📤 [SEND] " + req.toString(2));
} catch (Exception e) {
System.err.println("❌ Error sending request: " + e.getMessage());
}
}
public static Socket getSocket() {
return socket;
}
public static void main(String[] args) { public static void main(String[] args) {
new TelegramClient().start(); new TelegramClient().start();
} }
public PrintWriter getOut() {
return out;
}
} }
@@ -576,7 +576,7 @@ public class ChannelDatabase {
public static boolean transferOwnership(UUID channelId, UUID newOwnerUUID) { public static boolean transferOwnership(UUID channelId, UUID newOwnerUUID) {
String sql = """ String updateRoles = """
UPDATE channel_subscribers UPDATE channel_subscribers
SET role = CASE SET role = CASE
WHEN user_id = ? THEN 'owner' WHEN user_id = ? THEN 'owner'
@@ -586,15 +586,64 @@ public class ChannelDatabase {
WHERE channel_id = ? WHERE channel_id = ?
"""; """;
String clearPermissions = """
UPDATE channel_subscribers
SET permissions = '{}'::jsonb
WHERE channel_id = ? AND user_id = ?
""";
try (Connection conn = ConnectionDb.connect()) {
conn.setAutoCommit(false);
try (PreparedStatement roleStmt = conn.prepareStatement(updateRoles);
PreparedStatement clearPermsStmt = conn.prepareStatement(clearPermissions)) {
roleStmt.setObject(1, newOwnerUUID);
roleStmt.setObject(2, channelId);
roleStmt.executeUpdate();
clearPermsStmt.setObject(1, channelId);
clearPermsStmt.setObject(2, newOwnerUUID);
clearPermsStmt.executeUpdate();
conn.commit();
return true;
} catch (SQLException e) {
conn.rollback();
e.printStackTrace();
}
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public static List<UUID> getChannelSubscriberUUIDs(UUID channelId) {
List<UUID> subscriberIds = new ArrayList<>();
try (Connection conn = ConnectionDb.connect(); try (Connection conn = ConnectionDb.connect();
PreparedStatement stmt = conn.prepareStatement(sql)) { PreparedStatement stmt = conn.prepareStatement("SELECT user_id FROM channel_subscribers WHERE channel_id = ?")) {
stmt.setObject(1, channelId);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
subscriberIds.add(UUID.fromString(rs.getString("user_id")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return subscriberIds;
}
stmt.setObject(1, newOwnerUUID); public static boolean updateAdminPermissions(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(2, channelId);
stmt.setObject(3, userId);
stmt.executeUpdate(); return stmt.executeUpdate() > 0;
return true;
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
return false; return false;
@@ -7,7 +7,7 @@ import java.sql.SQLException;
public class ConnectionDb { public class ConnectionDb {
private static final String JDBC_URL = "jdbc:postgresql://localhost:5432/Telegram"; private static final String JDBC_URL = "jdbc:postgresql://localhost:5432/Telegram";
private static final String USERNAME = "postgres"; private static final String USERNAME = "postgres";
private static final String PASSWORD = "124postpass"; private static final String PASSWORD = "Partow@1384";
public ConnectionDb() { public ConnectionDb() {
} }
@@ -34,6 +34,15 @@ public class ContactDatabase {
} }
} }
public static List<UUID> getContactUUIDs(UUID userId) {
List<Contact> contacts = getContacts(userId);
List<UUID> contactIds = new ArrayList<>();
for (Contact c : contacts) {
contactIds.add(c.getContact_id());
}
return contactIds;
}
public boolean removeContact(UUID user_id, UUID contact_id) { public boolean removeContact(UUID user_id, UUID contact_id) {
@@ -3,6 +3,7 @@ package org.to.telegramfinalproject.Database;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.to.telegramfinalproject.Models.Group; import org.to.telegramfinalproject.Models.Group;
import org.to.telegramfinalproject.Models.User;
import java.sql.*; import java.sql.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -377,7 +378,7 @@ public class GroupDatabase {
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
return "member"; // پیش‌فرض return "member";
} }
@@ -444,7 +445,7 @@ public class GroupDatabase {
stmt.setObject(2, userId); stmt.setObject(2, userId);
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
return rs.next(); // اگر رکوردی پیدا شد یعنی owner است return rs.next();
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -460,7 +461,7 @@ public class GroupDatabase {
ResultSet rs = stmt.executeQuery(); ResultSet rs = stmt.executeQuery();
if (rs.next()) { if (rs.next()) {
String role = rs.getString("role"); String role = rs.getString("role");
return "admin".equals(role) || "owner".equals(role); // owner هم admin هست return "admin".equals(role) || "owner".equals(role);
} }
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
@@ -508,7 +509,7 @@ public class GroupDatabase {
while (rs.next()) { while (rs.next()) {
JSONObject member = new JSONObject(); JSONObject member = new JSONObject();
member.put("profile_name", rs.getString("profile_name")); member.put("profile_name", rs.getString("profile_name"));
member.put("user_id", rs.getString("user_id")); // آیدی قابل نمایش member.put("user_id", rs.getString("user_id"));
member.put("internal_uuid", rs.getObject("internal_uuid").toString()); member.put("internal_uuid", rs.getObject("internal_uuid").toString());
member.put("role", rs.getString("role")); member.put("role", rs.getString("role"));
@@ -556,7 +557,7 @@ public class GroupDatabase {
public static boolean transferOwnership(UUID groupId, UUID newOwnerId) { public static boolean transferOwnership(UUID groupId, UUID newOwnerId) {
String demoteOldOwner = "UPDATE group_members SET role = 'admin' WHERE group_id = ? AND role = 'owner'"; 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 = ?"; String promoteNewOwner = "UPDATE group_members SET role = 'owner', permissions = '{}'::jsonb WHERE group_id = ? AND user_id = ?";
try (Connection conn = ConnectionDb.connect()) { try (Connection conn = ConnectionDb.connect()) {
conn.setAutoCommit(false); conn.setAutoCommit(false);
@@ -600,4 +601,82 @@ public class GroupDatabase {
} }
public static List<UUID> getGroupMemberUUIDs(UUID groupId) {
List<UUID> memberIds = new ArrayList<>();
try (Connection conn = ConnectionDb.connect();
PreparedStatement stmt = conn.prepareStatement("SELECT user_id FROM group_members WHERE group_id = ?")) {
stmt.setObject(1, groupId);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
memberIds.add(UUID.fromString(rs.getString("user_id")));
}
} catch (SQLException e) {
e.printStackTrace();
}
return memberIds;
}
public static boolean updateAdminPermissions(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 boolean isMember(UUID groupId, UUID userId) {
String sql = "SELECT 1 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();
return rs.next();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
// public static List<User> searchGroupMembers(UUID groupId, String keyword) {
// List<User> result = new ArrayList<>();
// String sql = """
// SELECT u.*
// FROM users u
// JOIN group_members gm ON gm.user_id = u.internal_uuid
// WHERE gm.group_id = ?
// AND (
// LOWER(u.profile_name) LIKE ?
// OR LOWER(u.username) LIKE ?
// OR LOWER(u.user_id) LIKE ?
// )
// """;
//
// try (Connection conn = ConnectionDb.connect();
// PreparedStatement stmt = conn.prepareStatement(sql)) {
//
// stmt.setObject(1, groupId);
// String likePattern = "%" + keyword.toLowerCase() + "%";
// stmt.setString(2, likePattern);
// stmt.setString(3, likePattern);
// stmt.setString(4, likePattern);
//
// ResultSet rs = stmt.executeQuery();
// while (rs.next()) {
// User user = User.fromResultSet(rs);
// result.add(user);
// }
//
// } catch (SQLException e) {
// e.printStackTrace();
// }
// return result;
// }
} }
@@ -9,13 +9,58 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
public class userDatabase { public class userDatabase {
public userDatabase() { public userDatabase() {
} }
private Connection getConnection() throws SQLException { public static boolean isUserOnline(UUID userId) {
String sql = "SELECT status FROM users WHERE internal_uuid = ?";
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, userId);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
String status = rs.getString("status");
return "online".equalsIgnoreCase(status);
}
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
private static Connection getConnection() throws SQLException {
return ConnectionDb.connect(); return ConnectionDb.connect();
} }
public static String getLastSeen(UUID userId) {
String sql = "SELECT last_seen FROM users WHERE internal_uuid = ?";
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, userId);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
Timestamp lastSeen = rs.getTimestamp("last_seen");
if (lastSeen != null) {
return lastSeen.toLocalDateTime().toString();
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return "Unknown";
}
public User findByUserId(String userId) { public User findByUserId(String userId) {
String query = "SELECT * FROM users WHERE user_id = ?"; String query = "SELECT * FROM users WHERE user_id = ?";
@@ -37,6 +37,10 @@ public class ChatEntry {
} }
public ChatEntry() {
}
// 🟩 گتر و ستر جدید // 🟩 گتر و ستر جدید
public boolean isOwner() { public boolean isOwner() {
return isOwner; return isOwner;
@@ -54,7 +58,6 @@ public class ChatEntry {
isAdmin = admin; isAdmin = admin;
} }
// سایر گترها
public UUID getId() { public UUID getId() {
return internalId; return internalId;
} }
@@ -87,4 +90,22 @@ public class ChatEntry {
public void setPermissions(JSONObject permissions) { public void setPermissions(JSONObject permissions) {
this.permissions = permissions; this.permissions = permissions;
} }
public void setName(String name) {this.name = name;
}
public void setDisplayId(String id) {this.displayId = id;
}
public void setImageUrl(String image_url) {this.imageUrl = image_url;
}
public void setType(String type) {this.type =type;
}
public void setId(String internalId) {this.internalId = UUID.fromString(internalId);
}
} }
@@ -6,6 +6,8 @@ public class ResponseModel {
private String status; private String status;
private String message; private String message;
private JSONObject data; private JSONObject data;
private String requestId;
public ResponseModel(String status, String message) { public ResponseModel(String status, String message) {
@@ -29,4 +31,20 @@ public class ResponseModel {
} }
public JSONObject getData() {return this.data;} public JSONObject getData() {return this.data;}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public JSONObject toJson() {
JSONObject json = new JSONObject();
json.put("status", this.status);
json.put("message", this.message);
json.put("data", this.data != null ? this.data : JSONObject.NULL);
if (this.requestId != null) {
json.put("request_id", this.requestId);
}
return json;
}
} }
File diff suppressed because it is too large Load Diff
@@ -176,7 +176,7 @@ public class RealTimeEventDispatcher {
public static void notifyAddedToChat(String type, UUID chatId, String chatName, String imageUrl, UUID userId) { public static void notifyAddedToChat(String type, UUID chatId, String chatName, String imageUrl, UUID userId) {
JSONObject data = new JSONObject(); JSONObject data = new JSONObject();
data.put("chat_type", type); // group یا channel data.put("chat_type", type);
data.put("chat_id", chatId.toString()); data.put("chat_id", chatId.toString());
data.put("chat_name", chatName); data.put("chat_name", chatName);
data.put("image_url", imageUrl); data.put("image_url", imageUrl);
@@ -246,5 +246,63 @@ public class RealTimeEventDispatcher {
broadcastToUsers(contacts, event); broadcastToUsers(contacts, event);
} }
public static void sendGroupOrChannelUpdate(String type, UUID chatId, String name, String imageUrl, String description, List<UUID> affectedUsers) {
JSONObject data = new JSONObject();
data.put("chat_type", type);
data.put("chat_id", chatId.toString());
data.put("name", name);
data.put("image_url", imageUrl);
data.put("description", description != null ? description : "");
JSONObject event = new JSONObject();
event.put("action", "chat_updated");
event.put("data", data);
broadcastToUsers(affectedUsers, event);
}
public static void notifyBecameAdmin(String type, UUID chatId, String chatName, String imageUrl, UUID userId) {
JSONObject data = new JSONObject();
data.put("chat_type", type); // group or channel
data.put("chat_id", chatId.toString());
data.put("chat_name", chatName);
data.put("image_url", imageUrl);
JSONObject event = new JSONObject();
event.put("action", "became_admin");
event.put("data", data);
sendToUser(userId, event);
}
public static void notifyRemovedAdminFromChat(String type, UUID chatId, String chatName, String imageUrl, UUID userId) {
JSONObject data = new JSONObject();
data.put("chat_type", type);
data.put("chat_id", chatId.toString());
data.put("chat_name", chatName);
data.put("image_url", imageUrl);
JSONObject event = new JSONObject();
event.put("action", "removed_admin");
event.put("data", data);
sendToUser(userId, event);
}
public static void sendOwnershipTransferred(String type, UUID chatId, String chatName, List<UUID> affectedUsers) {
JSONObject data = new JSONObject();
data.put("chat_type", type); // "group" or "channel"
data.put("chat_id", chatId.toString());
data.put("chat_name", chatName);
JSONObject event = new JSONObject();
event.put("action", "ownership_transferred");
event.put("data", data);
broadcastToUsers(affectedUsers, event);
}
} }
@@ -0,0 +1,107 @@
package org.to.telegramfinalproject.UI;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.json.JSONObject;
import org.to.telegramfinalproject.Client.ClientConnection;
import org.to.telegramfinalproject.Database.userDatabase;
import org.to.telegramfinalproject.Models.User;
import org.to.telegramfinalproject.Security.PasswordHashing;
import java.io.IOException;
public class LoginForm {
@FXML
private Button loginButton;
@FXML
private Button backButton;
@FXML private TextField usernameField;
@FXML private PasswordField passwordField;
private ClientConnection connection;
@FXML
public void initialize() {
try {
connection = new ClientConnection("localhost", 8000);
} catch (Exception e) {
System.out.println("Could not connect to server: " + e.getMessage());
}
loginButton.setOnAction(e -> {
String username = usernameField.getText();
String password = passwordField.getText();
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);
if (connection!=null) {
connection.send(request.toString());
}
userDatabase userDb = new userDatabase();
User user = userDb.findByUsername(username);
if(!userDb.existsByUsername(username)){
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid username");
alert.show();
}
else if(!PasswordHashing.verify(password,user.getPassword())){
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid password");
alert.show();
}
else if(!PasswordHashing.verify(password,user.getPassword()) && !userDb.existsByUsername(username)){
Alert alert = new Alert(Alert.AlertType.ERROR, "Invalid password and username");
alert.show();
}
else{
try {
String responseStr = connection.receive();
JSONObject response = new JSONObject(responseStr);
System.out.println("Status: " + response.getString("status"));
System.out.println("Message: " + response.getString("message"));
Alert alert = new Alert(Alert.AlertType.INFORMATION, " Message: " + response.getString("message"));
alert.show();
} catch (Exception ex) {
Alert alert = new Alert(Alert.AlertType.ERROR, "Error receiving response: " + ex.getMessage());
alert.show();
}
}
});
backButton.setOnAction(e -> {
switchScene("login_view.fxml");
});
}
private void switchScene(String fxmlFile) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile));
Parent root = loader.load();
Stage stage = (Stage) backButton.getScene().getWindow();
stage.setScene(new Scene(root));
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,110 @@
package org.to.telegramfinalproject.UI;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.json.JSONObject;
import org.to.telegramfinalproject.Client.ClientConnection;
import org.to.telegramfinalproject.Database.userDatabase;
import java.io.IOException;
public class RegisterForm {
@FXML
private Button submitButton;
@FXML
private Button backButton;
@FXML private TextField userIdField;
@FXML private TextField usernameField;
@FXML private TextField profileNameField;
@FXML private PasswordField passwordField;
@FXML private PasswordField confirmPasswordField;
private ClientConnection connection;
@FXML
public void initialize() {
try {
connection = new ClientConnection("localhost", 8000);
} catch (Exception e) {
System.out.println("Could not connect to server: " + e.getMessage());
}
submitButton.setOnAction(e -> {
String userID = userIdField.getText();
String username = usernameField.getText();
String profile_name = profileNameField.getText();
String password = passwordField.getText();
String confirmPass =confirmPasswordField.getText();
JSONObject request = new JSONObject();
String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b";
userDatabase userDb = new userDatabase();
if(password.equals(confirmPass) && password.matches(passwordRegex) && !userDb.existsByUserId(userID)&& !userDb.existsByUsername(username)){
try {
request.put("action", "register");
request.put("user_id", userID);
request.put("username", username);
request.put("password", password);
request.put("profile_name", profile_name);
connection.send(request.toString());
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration is successful");
alert.show();
} catch (Exception ex) {
Alert alert = new Alert(Alert.AlertType.ERROR, "Error receiving response: " + ex.getMessage());
alert.show();
}
}
else if(!password.equals(confirmPass) && password.matches(passwordRegex)) {
Alert alert = new Alert(Alert.AlertType.ERROR, "Password doesn't match");
alert.show();
}
else if(userDb.existsByUserId(userID))
{
Alert alert = new Alert(Alert.AlertType.ERROR, "User ID is already exist");
alert.show();
}
else if(userDb.existsByUsername(username)){
Alert alert = new Alert(Alert.AlertType.ERROR, "Username is already exist");
alert.show();
}
else {
Alert alert = new Alert(Alert.AlertType.ERROR, "Password isn't Strong enough");
alert.show();
}
});
backButton.setOnAction(e -> {
switchScene("login_view.fxml");
});
}
private void switchScene(String fxmlFile) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile));
Parent root = loader.load();
Stage stage = (Stage) backButton.getScene().getWindow();
stage.setScene(new Scene(root));
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
}