Merge branch 'develop' into Sidebar-Menu

# Conflicts:
#	src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java
#	src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java
#	src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java
This commit is contained in:
Asal Lotfi
2025-08-03 18:58:29 +03:30
16 changed files with 1882 additions and 174 deletions
@@ -2,7 +2,9 @@ package org.to.telegramfinalproject.Client;
import org.json.JSONArray;
import org.json.JSONObject;
import org.to.telegramfinalproject.Database.PrivateChatDatabase;
import org.to.telegramfinalproject.Models.ChatEntry;
import org.to.telegramfinalproject.Models.ContactEntry;
import org.to.telegramfinalproject.Models.SearchRequestModel;
import org.to.telegramfinalproject.Models.SearchResultModel;
@@ -25,6 +27,8 @@ public class ActionHandler {
public static ActionHandler instance;
private void handleRealTime(JSONObject json) throws IOException {
IncomingMessageListener listener = new IncomingMessageListener(this.in);
listener.handleRealTimeEvent (json);
@@ -440,8 +444,12 @@ public class ActionHandler {
Session.currentUser = response.getJSONObject("data");
JSONArray chatListJson = Session.currentUser.getJSONArray("chat_list");
JSONArray Archived = Session.currentUser.getJSONArray("archived_chat_list");
JSONArray Active = Session.currentUser.getJSONArray("active_chat_list");
JSONArray contactList = Session.currentUser.getJSONArray("contact_list");
List<ChatEntry> chatList = new ArrayList<>();
for (Object obj : chatListJson) {
JSONObject chat = (JSONObject) obj;
@@ -454,15 +462,80 @@ public class ActionHandler {
chat.isNull("last_message_time") ? null : LocalDateTime.parse(chat.getString("last_message_time")),
chat.optBoolean("is_owner", false),
chat.optBoolean("is_admin", false)
);
if (chat.has("other_user_id")) {
entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id")));
}
chatList.add(entry);
}
List<ChatEntry> archivedChats = new ArrayList<>();
for (Object obj : Archived){
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)
);
if (chat.has("other_user_id")) {
entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); // 👈 اضافه کردن برای private chat
}
archivedChats.add(entry);
}
List<ChatEntry> activeChats = new ArrayList<>();
for (Object obj : Active){
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)
);
if (chat.has("other_user_id")) {
entry.setOtherUserId(UUID.fromString(chat.getString("other_user_id"))); // 👈 اضافه کردن برای private chat
}
activeChats.add(entry);
}
Session.contactEntries.clear();
for (Object obj : contactList) {
JSONObject c = (JSONObject) obj;
ContactEntry entry = new ContactEntry(
UUID.fromString(c.getString("contact_id")),
c.getString("user_id"),
c.getString("profile_name"),
c.optString("image_url", ""),
c.optBoolean("is_blocked", false)
);
Session.contactEntries.add(entry);
}
Session.activeChats = activeChats;
Session.archivedChats = archivedChats;
Session.chatList = chatList;
break;
@@ -661,12 +734,14 @@ public class ActionHandler {
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 senderName = m.optString("sender_name", "Other");
String content = m.getString("content");
String time = m.getString("send_at");
String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : "Other";
String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : senderName;
System.out.println("[" + time + "] " + label + ": " + content);
}
System.out.println("─────────────────────────────────────────────");
@@ -703,8 +778,9 @@ public class ActionHandler {
System.out.println("2. Search");
System.out.println("3. Create Channel");
System.out.println("4. Create group");
System.out.println("5. Show sidebar menu");
System.out.println("6. Logout");
System.out.println("5. View contacts");
System.out.println("6. Show sidebar menu");
System.out.println("7. Logout");
System.out.print("Choose an option: ");
String choice = scanner.nextLine();
@@ -717,8 +793,9 @@ public class ActionHandler {
case "2" -> search();
case "3" -> createChannel();
case "4" -> createGroup();
case "5" -> showSidebarMenu();
case "6" -> {
case "5" -> showContactList();
case "6" -> showSidebarMenu();
case "7" -> {
logout();
return;
}
@@ -727,39 +804,262 @@ public class ActionHandler {
}
}
public void showChatListAndSelect() {
List<ChatEntry> chatList = Session.getChatList();
if (chatList.isEmpty()) {
System.out.println("📭 You have no chats.");
public void showContactList() {
List<ContactEntry> contacts = Session.contactEntries;
if (contacts.isEmpty()) {
System.out.println("📭 You have no contacts.");
return;
}
System.out.println("\nYour Chats:");
for (int i = 0; i < chatList.size(); i++) {
ChatEntry entry = chatList.get(i);
String last = entry.getLastMessageTime() == null ? "No messages yet" : entry.getLastMessageTime().toString();
System.out.printf("%d. [%s] %s - Last: %s\n", i + 1, entry.getType(), entry.getName(), last);
System.out.println("👥 Your Contacts:");
for (int i = 0; i < contacts.size(); i++) {
System.out.println((i + 1) + ". " + contacts.get(i));
}
System.out.print("Select a chat by number: ");
int choice = Integer.parseInt(scanner.nextLine());
if (choice < 1 || choice > chatList.size()) {
System.out.print("Select a contact (0 to go back): ");
int choice = scanner.nextInt();
scanner.nextLine();
if (choice == 0) return;
if (choice < 1 || choice > contacts.size()) {
System.out.println("❌ Invalid choice.");
return;
}
openChat(chatList.get(choice - 1));
ContactEntry selected = contacts.get(choice - 1);
System.out.println("\n📇 What do you want to do with " + selected.getProfileName() + "?");
System.out.println("1. View Profile");
System.out.println("2. Send Message");
System.out.print("Enter your choice: ");
int action = scanner.nextInt();
scanner.nextLine();
switch (action) {
case 1 -> viewProfile(selected.getContactId());
case 2 -> startPrivateChat(selected);
default -> System.out.println("❌ Invalid option.");
}
}
private void viewProfile(UUID targetId) {
JSONObject req = new JSONObject();
req.put("action", "view_profile");
req.put("target_id", targetId.toString());
JSONObject res = sendWithResponse(req);
if (res == null || !res.getString("status").equals("success")) {
System.out.println("❌ Failed to fetch profile.");
return;
}
JSONObject data = res.getJSONObject("data");
String profileName = data.getString("profile_name");
String userId = data.getString("user_id");
String bio = data.optString("bio", "(no bio)");
String imageUrl = data.optString("image_url", "(no image)");
boolean isOnline = data.getBoolean("is_online");
String lastSeen = data.getString("last_seen");
System.out.println("\n📄 Profile Info:");
System.out.println("Name: " + profileName);
System.out.println("User ID: " + userId);
System.out.println("Bio: " + bio);
System.out.println("Image: " + imageUrl);
System.out.println("Status: " + (isOnline ? "🟢 Online" : "🔘 Last seen at " + lastSeen));
}
private void startPrivateChat(ContactEntry contact) {
UUID myId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
UUID contactId = contact.getContactId();
JSONObject req = new JSONObject();
req.put("action", "get_or_create_private_chat");
req.put("user1", myId.toString());
req.put("user2", contactId.toString());
JSONObject res = sendWithResponse(req);
if (res == null || !res.getString("status").equals("success")) {
System.out.println("❌ Failed to create or fetch private chat.");
return;
}
JSONObject data = res.getJSONObject("data");
UUID chatId = UUID.fromString(data.getString("chat_id"));
ChatEntry entry = new ChatEntry(
chatId,
contact.getUserId(),
contact.getProfileName(),
contact.getImageUrl(),
"private",
null,
false,
false
);
entry.setOtherUserId(contactId);
Session.chatList.add(0, entry); // اضافه به اول لیست
System.out.println("✅ Chat with " + contact.getProfileName() + " started.");
openChat(entry); // 👈 مستقیم وارد چت شو (اختیاری)
}
// public void showChatListAndSelect() {
//
//
// List<ChatEntry> chatList = Session.getChatList();
// if (chatList.isEmpty()) {
// System.out.println("📭 You have no chats.");
// return;
// }
//
// System.out.println("\nYour Chats:");
// for (int i = 0; i < chatList.size(); i++) {
// ChatEntry entry = chatList.get(i);
// String last = entry.getLastMessageTime() == null ? "No messages yet" : entry.getLastMessageTime().toString();
// System.out.printf("%d. [%s] %s - Last: %s\n", i + 1, entry.getType(), entry.getName(), last);
// }
//
// System.out.print("Select a chat by number: ");
// int choice = Integer.parseInt(scanner.nextLine());
// if (choice < 1 || choice > chatList.size()) {
// System.out.println("❌ Invalid choice.");
// return;
// }
//
// openChat(chatList.get(choice - 1));
// }
public void showChatListAndSelect() {
if (Session.activeChats == null || Session.activeChats.isEmpty()) {
System.out.println("No active chats.");
return;
}
System.out.println("\nYour Chats:");
System.out.println("0. 📦 Archived Chats");
for (int i = 0; i < Session.activeChats.size(); i++) {
ChatEntry entry = Session.activeChats.get(i);
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());
if (choice == 0) {
showArchivedChats();
return;
}
int index = choice - 1;
if (index < 0 || index >= Session.activeChats.size()) {
System.out.println("Invalid selection.");
return;
}
ChatEntry selected = Session.activeChats.get(index);
openChat(selected);
}
private void showArchivedChats() {
if (Session.archivedChats == null || Session.archivedChats.isEmpty()) {
System.out.println("📭 No archived chats.");
return;
}
System.out.println("\n📦 Archived Chats:");
for (int i = 0; i < Session.archivedChats.size(); i++) {
ChatEntry entry = Session.archivedChats.get(i);
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 (or 0 to return): ");
int choice = Integer.parseInt(scanner.nextLine());
if (choice == 0) {
System.out.println("🔙 Returning to main chat list...");
return;
}
int index = choice - 1;
if (index < 0 || index >= Session.archivedChats.size()) {
System.out.println("❌ Invalid selection.");
return;
}
ChatEntry selected = Session.archivedChats.get(index);
openChat(selected);
}
private void openChat(ChatEntry chat) {
//for private chats only
if (chat.getType().equalsIgnoreCase("private")) {
JSONObject reqTarget = new JSONObject();
reqTarget.put("action", "get_private_chat_target");
reqTarget.put("chat_id", chat.getId());
JSONObject resTarget = sendWithResponse(reqTarget);
if (resTarget == null || !resTarget.getString("status").equals("success")) {
System.out.println("❌ Failed to fetch target user for private chat.");
return;
}
String otherUserId = resTarget.getJSONObject("data").getString("target_id");
chat.setOtherUserId(UUID.fromString(otherUserId));
}
JSONObject req = new JSONObject();
req.put("action", "get_messages");
req.put("receiver_id", chat.getId());
req.put("receiver_id",chat.getId());
req.put("receiver_type", chat.getType());
// String type = chat.getType();
// UUID chatId = chat.getId();
// if (type.equals("private")) {
// UUID myId = UUID.fromString(Session.currentUser.getString("internal_uuid"));
// UUID otherUserId = chat.getId();
//
// JSONObject getChatIdReq = new JSONObject();
// getChatIdReq.put("action", "get_or_create_private_chat");
// getChatIdReq.put("user1", myId.toString());
// getChatIdReq.put("user2", otherUserId.toString());
//
// JSONObject chatIdRes = sendWithResponse(getChatIdReq);
// if (chatIdRes == null || !chatIdRes.getString("status").equals("success")) {
// System.out.println("❌ Failed to fetch private chat ID.");
// return;
// }
//
// Session.currentPrivateChatUserId = otherUserId;
// chatId = UUID.fromString(chatIdRes.getJSONObject("data").getString("chat_id"));
// chat.setId(String.valueOf(chatId));
//
// }
JSONObject res = sendWithResponse(req);
if (res == null || !res.getString("status").equals("success")) {
@@ -773,14 +1073,24 @@ public class ActionHandler {
for (int i = 0; i < messages.length(); i++) {
JSONObject m = messages.getJSONObject(i);
String senderId = m.getString("sender_id");
String senderName = m.optString("sender_name", "Other");
String content = m.getString("content");
String time = m.getString("send_at");
String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : "Other";
String label = senderId.equals(Session.currentUser.getString("internal_uuid")) ? "You" : senderName;
System.out.println("[" + time + "] " + label + ": " + content);
}
System.out.println("─────────────────────────────────────────────");
if (chat.getType().equals("private") && chat.getOtherUserId() == null) {
for (ContactEntry contact : Session.contactEntries) {
if (contact.getContactId().equals(chat.getId())) {
chat.setOtherUserId(contact.getContactId());
break;
}
}
}
boolean stayInChat = true;
@@ -832,9 +1142,24 @@ public class ActionHandler {
return false;
}
JSONObject reqTarget = new JSONObject();
reqTarget.put("action", "get_private_chat_target");
reqTarget.put("chat_id", chat.getId());
JSONObject resTarget = sendWithResponse(reqTarget);
if (resTarget == null || !resTarget.getString("status").equals("success")) {
System.out.println("❌ Failed to fetch target user for private chat.");
return false;
}
String otherUserId = resTarget.getJSONObject("data").getString("target_id");
chat.setOtherUserId(UUID.fromString(otherUserId));
JSONObject req = new JSONObject();
req.put("action", "view_profile");
req.put("target_id", chat.getId()); // internal UUID
req.put("target_id", chat.getOtherUserId()); // internal UUID
JSONObject res = sendWithResponse(req);
@@ -858,25 +1183,26 @@ public class ActionHandler {
System.out.println("3. Delete chat (one-sided)");
System.out.println("4. Delete chat (both sides)");
System.out.println("5. View profile");
System.out.println("6. Back");
System.out.println("6. Archive/Unarchived");
System.out.println("7. Back");
String input = scanner.nextLine();
switch (input) {
case "1" -> sendMessageTo(chat.getId(), "private");
case "2" -> toggleBlock(chat.getId());
case "1" -> sendMessage(chat.getId(), "private");
case "2" -> toggleBlock(chat.getOtherUserId());
case "3" -> {
deleteChat(chat.getId(), false);
return false;
return true;
}
case "4" -> {
deleteChat(chat.getId(), true);
return false;
return true;
}
case "5" -> {
JSONObject reqProfile = new JSONObject();
reqProfile.put("action", "view_profile");
reqProfile.put("target_id", chat.getId());
reqProfile.put("target_id", chat.getOtherUserId());
JSONObject resProfile = sendWithResponse(reqProfile);
@@ -899,7 +1225,13 @@ public class ActionHandler {
return true;
}
case "6" -> {
case "6" ->{
toggleArchive(chat.getId() , "private");
return true;
}
case "7" -> {
return false;
}
default -> System.out.println("Invalid choice.");
@@ -907,6 +1239,7 @@ public class ActionHandler {
return true;
}
private JSONObject getGroupPermissions(UUID groupId) {
JSONObject req = new JSONObject();
req.put("action", "get_group_permissions");
@@ -963,11 +1296,13 @@ public class ActionHandler {
System.out.println("11. Edit Admin Permissions");
}
System.out.println("12. Archive/Unarchived");
System.out.println("0. Back to Chat List");
String input = scanner.nextLine();
switch (input) {
case "1" -> sendMessageTo(chat.getId(), "group");
case "1" -> sendMessage(chat.getId(), "group");
case "2" -> viewGroupMembers(chat.getId());
case "3" -> {
if (isOwner || (isAdmin && perms.optBoolean("can_add_members", false)))
@@ -1012,6 +1347,10 @@ public class ActionHandler {
}
}
case "12"->{
toggleArchive(chat.getId() , "group");
}
case "0" -> {
return false;
}
@@ -1078,13 +1417,14 @@ public class ActionHandler {
System.out.println("11. Edit Admin Permissions");
}
System.out.println("12. Archive/Unarchived");
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");
sendMessage(chat.getId(), "channel");
} else {
System.out.println("❌ You don't have permission to post.");
}
@@ -1157,6 +1497,10 @@ public class ActionHandler {
}
}
case "12"->{
toggleArchive(chat.getId() , "channel");
}
case "0" -> {
return false;
@@ -1672,7 +2016,7 @@ public class ActionHandler {
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("chat_id", targetId.toString());
req.put("both", both);
send(req);
@@ -1687,6 +2031,8 @@ public class ActionHandler {
private void toggleBlock(UUID userId) {
JSONObject req = new JSONObject();
req.put("action", "toggle_block");
req.put("user_id", Session.getUserUUID());
@@ -1849,31 +2195,7 @@ public class ActionHandler {
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();
@@ -2328,6 +2650,9 @@ public class ActionHandler {
System.err.println("❌ Invalid request: missing action.");
return null;
}
System.out.println("📤 Sending request to server: " + request.toString(2));
System.out.println("📤 [sendWithResponse] Action: " + request.optString("action", "unknown") + ", Full: " + request.toString(2));
String requestId = UUID.randomUUID().toString();
request.put("request_id", requestId);
@@ -2391,13 +2716,22 @@ public class ActionHandler {
return;
}
System.out.println("\n💬 Your Chats:");
System.out.println("0. 📦 Archived chats");
int index = 1;
for (ChatEntry chat : Session.chatList) {
System.out.printf("%d. [%s] %s (%s)\n", index++, chat.getType(), chat.getName(), chat.getDisplayId());
for (ChatEntry chat : Session.activeChats) {
String time = (chat.getLastMessageTime() == null)
? "No messages yet"
: chat.getLastMessageTime().toString();
System.out.printf("%d. [%s] %s (%s) - Last: %s\n",
index++, chat.getType(), chat.getName(), chat.getDisplayId(), time);
}
}
public static class ChatStateMonitor implements Runnable {
private final PrintWriter out;
@@ -2714,6 +3048,173 @@ public class ActionHandler {
}
}
public void toggleArchive(UUID chatId, String chatType) {
// پیدا کردن چت از Session.chatList
Optional<ChatEntry> optional = Session.chatList.stream()
.filter(c -> c.getId().equals(chatId))
.findFirst();
if (optional.isEmpty()) {
System.out.println("❌ Chat not found.");
return;
}
ChatEntry chat = optional.get();
if (chat.isArchived()) {
unarchiveChat(chatId, chatType);
chat.setArchived(false);
System.out.println("✅ Chat unarchived.");
} else {
archiveChat(chatId, chatType);
chat.setArchived(true);
System.out.println("✅ Chat archived.");
}
Session.refreshChatLists();
}
private void archiveChat(UUID chatId, String chatType) {
JSONObject req = new JSONObject();
req.put("action", "archive_chat");
req.put("chat_id", chatId.toString());
req.put("chat_type", chatType);
JSONObject res = sendWithResponse(req);
if (res != null) System.out.println(res.getString("message"));
}
private void unarchiveChat(UUID chatId, String chatType) {
JSONObject req = new JSONObject();
req.put("action", "unarchive_chat");
req.put("chat_id", chatId.toString());
req.put("chat_type", chatType);
JSONObject res = sendWithResponse(req);
if (res != null) System.out.println(res.getString("message"));
}
// public void sendMessage(UUID receiverId, String receiverType) {
// Scanner scanner = new Scanner(System.in);
//
// System.out.print("Enter your message: ");
// String content = scanner.nextLine();
//
// System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE): ");
// String messageType = scanner.nextLine();
// Set<String> allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE");
// while (!allowedTypes.contains(messageType.toUpperCase())) {
// System.out.println("❌ Invalid message type. Try again (TEXT / IMAGE / VIDEO / FILE): ");
// messageType = scanner.nextLine();
// }
// messageType = messageType.toUpperCase();
//
// JSONArray attachmentsArray = new JSONArray();
//
// System.out.print("Do you want to attach files? (yes/no): ");
// if (scanner.nextLine().equalsIgnoreCase("yes")) {
// while (true) {
// System.out.print("File URL: ");
// String fileUrl = scanner.nextLine();
//
// System.out.print("File Type (IMAGE / VIDEO / FILE): ");
// String fileType = scanner.nextLine();
//
// JSONObject fileJson = new JSONObject();
// fileJson.put("file_url", fileUrl);
// fileJson.put("file_type", fileType);
// attachmentsArray.put(fileJson);
//
// System.out.print("Add another file? (yes/no): ");
// if (!scanner.nextLine().equalsIgnoreCase("yes")) {
// break;
// }
// }
// }
//
//
//
// JSONObject messageJson = new JSONObject();
// messageJson.put("action", "send_message");
// messageJson.put("receiver_type", receiverType);
// messageJson.put("content", content);
// messageJson.put("message_type", messageType);
// if (receiverType.equals("private")) {
// messageJson.put("receiver_user_id", receiverId.toString());
// } else {
// messageJson.put("receiver_id", receiverId.toString());
// }
//
// if (!attachmentsArray.isEmpty()) {
// messageJson.put("attachments", attachmentsArray);
// }
//
// JSONObject response = sendWithResponse(messageJson);
// if (response != null && response.getString("status").equals("success")) {
// System.out.println("✅ Message sent successfully! ID: " + response.getJSONObject("data").getString("message_id"));
// } else {
// System.out.println("❌ Failed to send message: " + (response != null ? response.getString("message") : "no response"));
// }
//
// }
public void sendMessage(UUID chatId, String receiverType) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter your message: ");
String content = scanner.nextLine();
System.out.print("Enter message type (TEXT / IMAGE / VIDEO / FILE): ");
String messageType = scanner.nextLine().toUpperCase();
Set<String> allowedTypes = Set.of("TEXT", "IMAGE", "VIDEO", "FILE");
while (!allowedTypes.contains(messageType)) {
System.out.print("❌ Invalid type. Try again (TEXT / IMAGE / VIDEO / FILE): ");
messageType = scanner.nextLine().toUpperCase();
}
JSONArray attachmentsArray = new JSONArray();
System.out.print("Do you want to attach files? (yes/no): ");
if (scanner.nextLine().equalsIgnoreCase("yes")) {
while (true) {
System.out.print("File URL: ");
String fileUrl = scanner.nextLine();
System.out.print("File Type (IMAGE / VIDEO / FILE): ");
String fileType = scanner.nextLine().toUpperCase();
JSONObject fileJson = new JSONObject();
fileJson.put("file_url", fileUrl);
fileJson.put("file_type", fileType);
attachmentsArray.put(fileJson);
System.out.print("Add another file? (yes/no): ");
if (!scanner.nextLine().equalsIgnoreCase("yes")) break;
}
}
// 🔹 فقط ارسال پیام با chat_id و receiver_type
JSONObject messageJson = new JSONObject();
messageJson.put("action", "send_message");
messageJson.put("receiver_type", receiverType);
messageJson.put("receiver_id", chatId.toString());
messageJson.put("content", content);
messageJson.put("message_type", messageType);
if (!attachmentsArray.isEmpty()) {
messageJson.put("attachments", attachmentsArray);
}
JSONObject response = sendWithResponse(messageJson);
if (response != null && response.getString("status").equals("success")) {
System.out.println("✅ Message sent! ID: " + response.getJSONObject("data").getString("message_id"));
} else {
System.out.println("❌ Failed to send message: " + (response != null ? response.getString("message") : "No response"));
}
}
private void showSidebarMenu() {
System.out.println("\n--- Sidebar Menu ---");
System.out.println("1. View Profile");
@@ -2760,4 +3261,7 @@ public class ActionHandler {
default -> System.out.println("Invalid action.");
}
}
}
@@ -107,7 +107,11 @@ public class IncomingMessageListener implements Runnable {
}
case "chat_updated" -> {
System.out.println("\n🔄 Group/Channel info updated.");
System.out.println("\n🔄 Chat info updated.");
if (msg.has("last_message_time")) {
updateLastMessageTime(msg);
} else {
new Thread(() -> {
try {
handleAdminRoleChanged(msg);
@@ -116,6 +120,8 @@ public class IncomingMessageListener implements Runnable {
}
}).start();
}
}
case "became_admin", "removed_admin", "ownership_transferred","admin_permissions_updated" -> {
System.out.println("🧩 Detected admin/owner role change. Calling handler...");
@@ -139,6 +145,68 @@ public class IncomingMessageListener implements Runnable {
System.out.print(">> ");
}
private void updateLastMessageTime(JSONObject msg) {
try {
UUID chatUUID = UUID.fromString(msg.getString("chat_id"));
String newTime = msg.optString("last_message_time", null);
Session.chatList.stream()
.filter(chat -> chat.getId().equals(chatUUID))
.findFirst()
.ifPresent(chat -> {
chat.setLastMessageTime(newTime);
System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId());
});
Session.activeChats.stream()
.filter(chat -> chat.getId().equals(chatUUID))
.findFirst()
.ifPresent(chat -> {
chat.setLastMessageTime(newTime);
System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId());
});
Session.archivedChats.stream()
.filter(chat -> chat.getId().equals(chatUUID))
.findFirst()
.ifPresent(chat -> {
chat.setLastMessageTime(newTime);
System.out.println("✅ Updated last message time for chat: " + chat.getDisplayId());
});
Session.chatList.sort((c1, c2) -> {
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
if (c1.getLastMessageTime() == null) return 1;
if (c2.getLastMessageTime() == null) return -1;
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending
});
Session.activeChats.sort((c1, c2) -> {
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
if (c1.getLastMessageTime() == null) return 1;
if (c2.getLastMessageTime() == null) return -1;
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending
});
Session.archivedChats.sort((c1, c2) -> {
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
if (c1.getLastMessageTime() == null) return 1;
if (c2.getLastMessageTime() == null) return -1;
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime()); // descending
});
if (Session.inChatListMenu) {
ActionHandler.displayChatList();
System.out.print("Select a chat by number: ");
}
} catch (Exception e) {
System.out.println("❌ Failed to update last message time: " + e.getMessage());
}
}
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)));
@@ -230,11 +298,26 @@ public class IncomingMessageListener implements Runnable {
private void displayRealTimeMessage(String action, JSONObject msg) {
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"));
System.out.println("\n🔔 New Message Received:");
String senderName = msg.optString("sender_name", "Unknown");
String content = msg.optString("content", "(empty)");
String sendAt = msg.optString("send_at", "-");
String receiverId = msg.optString("receiver_id", "");
String receiverType = msg.optString("receiver_type", "");
boolean isInCurrentChat = Session.inChatMenu &&
Session.currentChatId != null &&
Session.currentChatId.equals(receiverId);
if (isInCurrentChat) {
System.out.println(senderName + ": " + content + " (" + sendAt + ")");
} else {
System.out.println("💬 Message from " + senderName + " in " + receiverType + " chat: " + content);
Session.forceRefreshChatList = true;
}
}
case "message_edited" -> {
System.out.println("\n✏️ Message Edited:");
System.out.println("ID: " + msg.getString("message_id"));
@@ -3,7 +3,9 @@ package org.to.telegramfinalproject.Client;
import org.json.JSONArray;
import org.json.JSONObject;
import org.to.telegramfinalproject.Models.ChatEntry;
import org.to.telegramfinalproject.Models.ContactEntry;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -13,6 +15,9 @@ import java.util.UUID;
public class Session {
public static JSONObject currentUser;
public static List<ChatEntry> chatList = new ArrayList<>();
public static List<ChatEntry> archivedChats = new ArrayList<>();
public static List<ChatEntry> activeChats = new ArrayList<>();
public static UUID currentPrivateChatUserId = null;
public static volatile boolean forceRefreshChatList = false;
public static volatile boolean backToChatList = false;
public static boolean inChatListMenu = false;
@@ -21,7 +26,7 @@ public class Session {
public static volatile boolean refreshCurrentChatMenu = false;
public static String currentChatId = null;
public static ChatEntry currentChatEntry = null;
public static List<ContactEntry> contactEntries = new ArrayList<>();
@@ -33,26 +38,77 @@ public class Session {
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"));
// chatList.add(entry);
// }
// }
public static void updateChatList(JSONArray chatArray) {
chatList.clear();
for (int i = 0; i < chatArray.length(); i++) {
JSONObject obj = chatArray.getJSONObject(i);
LocalDateTime lastMessageTime = null;
if (obj.has("last_message_time") && !obj.isNull("last_message_time")) {
String timeStr = obj.getString("last_message_time");
if (!timeStr.isBlank()) {
lastMessageTime = LocalDateTime.parse(timeStr);
}
}
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)
lastMessageTime,
obj.optBoolean("is_owner", false),
obj.optBoolean("is_admin", false)
);
entry.setPermissions(obj.optJSONObject("permissions")); // اگر permissions وجود داره
entry.setPermissions(obj.optJSONObject("permissions"));
chatList.add(entry);
}
chatList.sort((c1, c2) -> {
if (c1.getLastMessageTime() == null && c2.getLastMessageTime() == null) return 0;
if (c1.getLastMessageTime() == null) return 1;
if (c2.getLastMessageTime() == null) return -1;
return c2.getLastMessageTime().compareTo(c1.getLastMessageTime());
});
activeChats = chatList.stream().filter(c -> !c.isArchived()).toList();
archivedChats = chatList.stream().filter(ChatEntry::isArchived).toList();
}
public static List<ChatEntry> getChatList() {
return chatList;
}
public static void refreshChatLists() {
Session.activeChats = Session.chatList.stream()
.filter(c -> !c.isArchived())
.toList();
Session.archivedChats = Session.chatList.stream()
.filter(ChatEntry::isArchived)
.toList();
}
}
@@ -0,0 +1,72 @@
package org.to.telegramfinalproject.Database;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ArchivedChatDatabase {
public static boolean archiveChat(UUID userId, UUID chatId, String chatType) {
String sql = "INSERT INTO archived_chats (user_id, chat_id, chat_type) VALUES (?, ?, ?) ON CONFLICT DO NOTHING";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, userId);
ps.setObject(2, chatId);
ps.setString(3, chatType);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static boolean unarchiveChat(UUID userId, UUID chatId) {
String sql = "DELETE FROM archived_chats WHERE user_id = ? AND chat_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, userId);
ps.setObject(2, chatId);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static boolean isChatArchived(UUID userId, UUID chatId) {
String sql = "SELECT 1 FROM archived_chats WHERE user_id = ? AND chat_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, userId);
ps.setObject(2, chatId);
ResultSet rs = ps.executeQuery();
return rs.next();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static List<UUID> getArchivedChats(UUID userId) {
List<UUID> list = new ArrayList<>();
String sql = "SELECT chat_id FROM archived_chats WHERE user_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, userId);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
list.add(UUID.fromString(rs.getString("chat_id")));
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
@@ -1,5 +1,6 @@
package org.to.telegramfinalproject.Database;
import org.to.telegramfinalproject.Models.FileAttachment;
import org.to.telegramfinalproject.Models.Message;
import java.sql.*;
@@ -12,41 +13,106 @@ 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""";
public static boolean insertMessage(UUID messageId, UUID senderId, UUID receiverId,
String receiverType, String content, String messageType) {
String sql = "INSERT INTO messages (message_id, sender_id, receiver_type, receiver_id, content, message_type) " +
"VALUES (?, ?, ?, ?, ?, ?)";
try (Connection conn = ConnectionDb.connect();
PreparedStatement stmt = conn.prepareStatement(sql)) {
PreparedStatement ps = 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());
ps.setObject(1, messageId);
ps.setObject(2, senderId);
ps.setString(3, receiverType);
ps.setObject(4, receiverId);
ps.setString(5, content);
ps.setString(6, messageType);
stmt.executeUpdate();
return ps.executeUpdate() > 0;
} catch (SQLException e) {
System.err.println("❌ Error saving message: " + e.getMessage());
e.printStackTrace();
return false;
}
}
public static boolean insertAttachments(UUID messageId, List<FileAttachment> attachments) {
String sql = "INSERT INTO message_attachments (attachment_id, message_id, file_url, file_type) " +
"VALUES (?, ?, ?, ?)";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
for (FileAttachment att : attachments) {
ps.setObject(1, UUID.randomUUID());
ps.setObject(2, messageId);
ps.setString(3, att.getFileUrl());
ps.setString(4, att.getFileType());
ps.addBatch();
}
ps.executeBatch();
return true;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static void markGloballyDeleted(UUID chatId) {
String sql = "UPDATE messages SET is_deleted_globally = true WHERE receiver_id = ? AND receiver_type = 'private'";
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, chatId);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void logDeletedMessagesFor(UUID chatId, UUID userId) {
List<Message> messages = MessageDatabase.privateChatHistory(chatId);
for (Message message : messages) {
if (!isMessageDeleted(message.getMessage_id(), userId)) {
String sql = """
INSERT INTO deleted_messages (message_id, user_id)
VALUES (?, ?)
ON CONFLICT (message_id, user_id) DO NOTHING
""";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, message.getMessage_id());
ps.setObject(2, userId);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public static boolean isMessageDeleted(UUID messageId, UUID userId) {
String sql = "SELECT 1 FROM deleted_messages WHERE message_id = ? AND user_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, messageId);
ps.setObject(2, userId);
ResultSet rs = ps.executeQuery();
return rs.next();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
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();
@@ -100,7 +166,6 @@ public class MessageDatabase {
UUID.fromString(rs.getString("receiver_id")),
rs.getString("content"),
rs.getString("message_type"),
rs.getString("file_url"),
rs.getTimestamp("send_at").toLocalDateTime(),
rs.getString("status"),
rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
@@ -118,6 +183,31 @@ public class MessageDatabase {
return messages;
}
public static List<FileAttachment> getAttachments(UUID messageId) {
List<FileAttachment> attachments = new ArrayList<>();
String sql = "SELECT file_url, file_type FROM message_attachments WHERE message_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, messageId);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
attachments.add(new FileAttachment(
rs.getString("file_url"),
rs.getString("file_type")
));
}
} catch (SQLException e) {
e.printStackTrace();
}
return attachments;
}
public static LocalDateTime getLastMessageTimeBetween(UUID user1, UUID user2, String type) {
String sql = """
@@ -177,7 +267,6 @@ public class MessageDatabase {
UUID.fromString(rs.getString("receiver_id")),
rs.getString("content"),
rs.getString("message_type"),
rs.getString("file_url"),
rs.getTimestamp("send_at").toLocalDateTime(),
rs.getString("status"),
(UUID) rs.getObject("reply_to_id"),
@@ -217,25 +306,18 @@ public class MessageDatabase {
return result;
}
public static List<Message> privateChatHistory(UUID user1, UUID user2) {
public static List<Message> privateChatHistory(UUID chatId) {
List<Message> result = new ArrayList<>();
String sql = """
SELECT * FROM messages
WHERE receiver_type = 'private'
AND (
(sender_id = ? AND receiver_id = ?)
OR (sender_id = ? AND receiver_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);
stmt.setObject(1, chatId);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
result.add(extractMessage(rs));
@@ -248,6 +330,7 @@ public class MessageDatabase {
public static List<Message> groupChatHistory(UUID groupId) {
List<Message> result = new ArrayList<>();
String sql = """
@@ -360,4 +443,42 @@ public class MessageDatabase {
}
public List<Message> getMessagesForPrivateChat(UUID user1, UUID user2) {
UUID chatId = PrivateChatDatabase.findChatIdByUsers(user1, user2);
if (chatId == null) return new ArrayList<>();
return findByReceiver("private", chatId);
}
public static List<Message> findByReceiver(String receiverType, UUID receiverId) {
List<Message> messages = new ArrayList<>();
String sql = "SELECT * FROM messages WHERE receiver_type = ? AND receiver_id = ? ORDER BY send_at";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, receiverType);
ps.setObject(2, receiverId);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
UUID messageId = (UUID) rs.getObject("message_id");
UUID senderId = (UUID) rs.getObject("sender_id");
UUID recId = (UUID) rs.getObject("receiver_id");
String type = rs.getString("receiver_type");
String content = rs.getString("content");
String messageType = rs.getString("message_type");
LocalDateTime sendAt = rs.getTimestamp("send_at").toLocalDateTime();
Message message = new Message(messageId, senderId, recId, type, content, messageType, sendAt);
messages.add(message);
}
} catch (SQLException e) {
e.printStackTrace();
}
return messages;
}
}
@@ -1,5 +1,12 @@
package org.to.telegramfinalproject.Database;
import org.to.telegramfinalproject.Models.PrivateChat;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -8,6 +15,24 @@ import java.util.UUID;
public class PrivateChatDatabase {
public static List<UUID> getMembers(UUID privateChatId) {
String sql = "SELECT user1_id, user2_id FROM private_chat WHERE chat_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, privateChatId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
UUID user1 = (UUID) rs.getObject("user1_id");
UUID user2 = (UUID) rs.getObject("user2_id");
return new ArrayList<>(Arrays.asList(user1, user2));
}
} catch (SQLException e) {
e.printStackTrace();
}
return new ArrayList<>();
}
public static UUID getOrCreateSavedMessagesChat(UUID userId) {
String query = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?";
try (Connection conn = ConnectionDb.connect()) {
@@ -35,4 +60,230 @@ public class PrivateChatDatabase {
return null;
}
}
public static UUID findChatIdByUsers(UUID user1, UUID user2) {
UUID u1 = user1.compareTo(user2) < 0 ? user1 : user2;
UUID u2 = user1.compareTo(user2) < 0 ? user2 : user1;
String sql = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, u1);
ps.setObject(2, u2);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
return (UUID) rs.getObject("chat_id");
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static UUID getOrCreateChat(UUID user1, UUID user2) {
UUID u1 = user1.compareTo(user2) < 0 ? user1 : user2;
UUID u2 = user1.compareTo(user2) < 0 ? user2 : user1;
String select = "SELECT chat_id FROM private_chat WHERE user1_id = ? AND user2_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(select)) {
ps.setObject(1, u1);
ps.setObject(2, u2);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
return UUID.fromString(rs.getString("chat_id"));
}
} catch (SQLException e) {
e.printStackTrace();
}
UUID newChatId = UUID.randomUUID();
String insert = "INSERT INTO private_chat(chat_id, user1_id, user2_id) VALUES (?, ?, ?)";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(insert)) {
ps.setObject(1, newChatId);
ps.setObject(2, u1);
ps.setObject(3, u2);
ps.executeUpdate();
return newChatId;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public static List<PrivateChat> findChatsOfUser(UUID userId) {
List<PrivateChat> chats = new ArrayList<>();
String sql = "SELECT * FROM private_chat WHERE user1_id = ? OR user2_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, userId);
ps.setObject(2, userId);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
UUID chatId = (UUID) rs.getObject("chat_id");
UUID user1 = (UUID) rs.getObject("user1_id");
UUID user2 = (UUID) rs.getObject("user2_id");
chats.add(new PrivateChat(chatId, user1, user2));
}
} catch (SQLException e) {
e.printStackTrace();
}
return chats;
}
public static UUID getOtherUserInChat(UUID chatId, UUID currentUserId) {
String sql = "SELECT user1_id, user2_id FROM private_chat WHERE chat_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, chatId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
UUID user1 = (UUID) rs.getObject("user1_id");
UUID user2 = (UUID) rs.getObject("user2_id");
return currentUserId.equals(user1) ? user2 : user1;
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
private static PrivateChat extractPrivateChat(ResultSet rs) throws SQLException {
UUID chatId = UUID.fromString(rs.getString("chat_id"));
UUID user1 = rs.getObject("user1_id", UUID.class);
UUID user2 = rs.getObject("user2_id", UUID.class);
boolean user1Deleted = rs.getBoolean("user1_deleted");
boolean user2Deleted = rs.getBoolean("user2_deleted");
LocalDateTime createdAt = rs.getTimestamp("created_at").toLocalDateTime();
return new PrivateChat(chatId, user1, user2, user1Deleted, user2Deleted, createdAt);
}
public static void markChatDeleted(UUID userId, UUID chatId) {
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 chat_id = ?
""";
try (Connection conn = ConnectionDb.connect();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, userId);
stmt.setObject(2, userId);
stmt.setObject(3, chatId);
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void unmarkChatDeleted(UUID userId, UUID chatId) {
String sql = """
UPDATE private_chat
SET user1_deleted = CASE WHEN user1_id = ? THEN false ELSE user1_deleted END,
user2_deleted = CASE WHEN user2_id = ? THEN false ELSE user2_deleted END
WHERE chat_id = ?
""";
try (Connection conn = ConnectionDb.connect();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setObject(1, userId);
stmt.setObject(2, userId);
stmt.setObject(3, chatId);
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void markUser1Deleted(UUID chatId) {
updateBoolField(chatId, "user1_deleted", true);
}
public static void markUser2Deleted(UUID chatId) {
updateBoolField(chatId, "user2_deleted", true);
}
public static void markBothDeleted(UUID chatId) {
String sql = "UPDATE private_chat SET user1_deleted = true, user2_deleted = true WHERE chat_id = ?";
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, chatId);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void updateBoolField(UUID chatId, String field, boolean value) {
String sql = "UPDATE private_chat SET " + field + " = ? WHERE chat_id = ?";
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setBoolean(1, value);
ps.setObject(2, chatId);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static PrivateChat findById(UUID chatId) {
String sql = "SELECT * FROM private_chat WHERE chat_id = ?";
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, chatId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
UUID user1_id = (UUID) rs.getObject("user1_id");
UUID user2_id = (UUID) rs.getObject("user2_id");
boolean user1_deleted = rs.getBoolean("user1_deleted");
boolean user2_deleted = rs.getBoolean("user2_deleted");
LocalDateTime created_at = rs.getTimestamp("created_at").toLocalDateTime();
return new PrivateChat(chatId, user1_id, user2_id, user1_deleted, user2_deleted, created_at);
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static void clearDeletedFlag(UUID senderId, UUID chatId) {
String sql = """
UPDATE private_chat
SET user1_deleted = CASE WHEN user1_id = ? THEN FALSE ELSE user1_deleted END,
user2_deleted = CASE WHEN user2_id = ? THEN FALSE ELSE user2_deleted END
WHERE chat_id = ?
""";
try (Connection conn = ConnectionDb.connect(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, senderId);
ps.setObject(2, senderId);
ps.setObject(3, chatId);
int rows = ps.executeUpdate();
System.out.println("✅ clearDeletedFlag updated rows = " + rows);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@@ -313,6 +313,8 @@ public class userDatabase {
return null;
}
public List<User> searchUsers(String keyword, UUID currentUserId) {
String query = """
SELECT * FROM users
@@ -12,8 +12,10 @@ public class ChatEntry {
private String imageUrl;
private String type;
private LocalDateTime lastMessageTime;
private boolean archived = false;
private UUID otherUser;
// 🔹 نقش‌ها
private boolean isOwner = false;
private boolean isAdmin = false;
private JSONObject permissions;
@@ -28,7 +30,6 @@ public class ChatEntry {
this.lastMessageTime = lastMessageTime;
}
// ✅ کانستراکتور اضافه‌شده برای پشتیبانی از نقش‌ها (اختیاری، برای استفاده‌های جدید)
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;
@@ -41,7 +42,6 @@ public class ChatEntry {
}
// 🟩 گتر و ستر جدید
public boolean isOwner() {
return isOwner;
}
@@ -108,4 +108,36 @@ public class ChatEntry {
public boolean isArchived() {
return archived;
}
public void setArchived(boolean archived) {
this.archived = archived;
}
// public void setLastMessageTime(String newTime) {this.lastMessageTime = LocalDateTime.parse(newTime);
// }
public void setLastMessageTime(String newTime) {
if (newTime == null || newTime.isBlank()) {
this.lastMessageTime = null;
return;
}
try {
this.lastMessageTime = LocalDateTime.parse(newTime);
} catch (Exception e) {
System.out.println("❌ Failed to parse lastMessageTime: " + newTime);
this.lastMessageTime = null;
}
}
public void setOtherUserId(UUID otherId) {this.otherUser = otherId;
}
public UUID getOtherUserId(){
return otherUser;
}
}
@@ -0,0 +1,44 @@
package org.to.telegramfinalproject.Models;
import java.util.UUID;
public class ContactEntry {
private UUID contactId; // internal UUID
private String userId; // public ID
private String profileName;
private String imageUrl;
private boolean isBlocked;
public ContactEntry(UUID contactId, String userId, String profileName, String imageUrl, boolean isBlocked) {
this.contactId = contactId;
this.userId = userId;
this.profileName = profileName;
this.imageUrl = imageUrl;
this.isBlocked = isBlocked;
}
public UUID getContactId() {
return contactId;
}
public String getUserId() {
return userId;
}
public String getProfileName() {
return profileName;
}
public String getImageUrl() {
return imageUrl;
}
public boolean isBlocked() {
return isBlocked;
}
@Override
public String toString() {
return profileName + " (" + userId + ")" + (isBlocked ? " [Blocked]" : "");
}
}
@@ -0,0 +1,19 @@
package org.to.telegramfinalproject.Models;
public class FileAttachment {
private String fileUrl;
private String fileType;
public FileAttachment(String fileUrl, String fileType) {
this.fileUrl = fileUrl;
this.fileType = fileType;
}
public String getFileUrl() {
return fileUrl;
}
public String getFileType() {
return fileType;
}
}
@@ -54,7 +54,6 @@ public class JsonUtil {
obj.put("receiver_id", message.getReceiver_id().toString());
obj.put("content", message.getContent());
obj.put("message_type", message.getMessage_type());
obj.put("file_url", message.getFile_url());
obj.put("send_at", message.getSend_at().toString());
obj.put("status", message.getStatus());
obj.put("reply_to_id", message.getReply_to_id() != null ? message.getReply_to_id().toString() : JSONObject.NULL);
@@ -1,6 +1,7 @@
package org.to.telegramfinalproject.Models;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
public class Message {
@@ -10,7 +11,6 @@ public class Message {
private UUID receiver_id;
private String content;
private String message_type; // TEXT, IMAGE, FILE, ...
private String file_url;
private LocalDateTime send_at;
private String status; // SENT, DELIVERED, READ
private UUID reply_to_id;
@@ -18,9 +18,10 @@ public class Message {
private UUID original_message_id;
private UUID forwarded_by;
private UUID forwarded_from;
private List<FileAttachment> attachments;
public Message(UUID message_id, UUID sender_id, String receiver_type, UUID receiver_id, String content,
String message_type, String file_url, LocalDateTime send_at, String status,
String message_type, LocalDateTime send_at, String status,
UUID reply_to_id, boolean is_edited, UUID original_message_id,
UUID forwarded_by, UUID forwarded_from) {
this.message_id = message_id;
@@ -29,7 +30,6 @@ public class Message {
this.receiver_id = receiver_id;
this.content = content;
this.message_type = message_type;
this.file_url = file_url;
this.send_at = send_at;
this.status = status;
this.reply_to_id = reply_to_id;
@@ -39,6 +39,16 @@ public class Message {
this.forwarded_from = forwarded_from;
}
public Message(UUID messageId, UUID senderId, UUID receiverId, String receiverType, String content, String messageType, LocalDateTime now) {
this.message_id = messageId;
this.sender_id = senderId;
this.receiver_id = receiverId;
this.receiver_type = receiverType;
this.content = content;
this.message_type = messageType;
this.send_at = now;
}
public UUID getMessage_id() {
return message_id;
@@ -85,14 +95,6 @@ public class Message {
this.message_type = message_type;
}
public String getFile_url() {
return file_url;
}
public void setFile_url(String file_url) {
this.file_url = file_url;
}
public LocalDateTime getSend_at() {
return send_at;
}
@@ -148,4 +150,11 @@ public class Message {
public void setForwarded_from(UUID forwarded_from) {
this.forwarded_from = forwarded_from;
}
public void setAttachments(List<FileAttachment> attachments) {
this.attachments = attachments;
}
public List<FileAttachment> getAttachments() {
return attachments;
}
}
@@ -5,17 +5,29 @@ import java.util.UUID;
public class PrivateChat {
private final UUID chat_id;
private boolean user1_deleted;
private boolean user2_deleted;
private UUID user1_id;
private UUID user2_id;
private LocalDateTime created_at;
public PrivateChat(UUID chat_id, UUID user1_id, UUID user2_id, LocalDateTime created_at){
public PrivateChat(UUID chat_id, UUID user1_id, UUID user2_id){
this.chat_id = chat_id;
this.user1_id =user1_id;
this.user2_id =user2_id;
this.created_at =created_at;
}
public PrivateChat(UUID chatId, UUID user1, UUID user2, boolean user1Deleted, boolean user2Deleted, LocalDateTime createdAt) {
this.chat_id = chatId;
this.user1_id = user1;
this.user2_id = user2;
this.user1_deleted = user1Deleted;
this.user2_deleted = user2Deleted;
this.created_at = createdAt;
}
public void setUser1_id(UUID user1_id){this.user1_id =user1_id;}
public void setUser2_id(UUID user2_id){this.user2_id =user2_id;}
public void setCreated_at(LocalDateTime created_at){this.created_at = created_at;}
@@ -87,36 +87,126 @@ public class ClientHandler implements Runnable {
List<Group> groups = GroupDatabase.getGroupsByUser(user.getInternal_uuid());
List<Channel> channels = ChannelDatabase.getChannelsByUser(user.getInternal_uuid());
List<Message> unreadMessages = MessageDatabase.getUnreadMessages(user.getInternal_uuid());
List<UUID> archivedChatIds = ArchivedChatDatabase.getArchivedChats(user.getInternal_uuid());
user.setContactList(contacts);
user.setGroupList(groups);
user.setChannelList(channels);
user.setUnreadMessages(unreadMessages);
List<ChatEntry> chatList = new ArrayList<>();
List<ChatEntry> archivedChatList = new ArrayList<>();
List<ChatEntry> activeChatList = new ArrayList<>();
JSONArray contactList = new JSONArray();
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(),
JSONObject c = new JSONObject();
c.put("user_id", contact.getUser_id().toString());
c.put("contact_id", contact.getContact_id().toString());
c.put("is_blocked", contact.getIs_blocked());
c.put("profile_name", target.getProfile_name());
c.put("image_url", target.getImage_url());
contactList.put(c);
}
// 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
//// ));
//
// UUID targetId = target.getInternal_uuid();
//
// ChatEntry entry = new ChatEntry(
// targetId,
// target.getUser_id(),
// target.getProfile_name(),
// target.getImage_url(),
// "private",
// last,
// false,
// false
// );
//
//
//
// }
List<PrivateChat> privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid());
for (PrivateChat chat : privateChats) {
UUID otherId = chat.getUser1_id().equals(currentUser.getInternal_uuid()) ?
chat.getUser2_id() : chat.getUser1_id();
User otherUser = userDatabase.findByInternalUUID(otherId);
if (otherUser == null) continue;
LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private");
ChatEntry entry = new ChatEntry(
chat.getChat_id(),
otherUser.getUser_id(),
otherUser.getProfile_name(),
otherUser.getImage_url(),
"private",
last,
lastMessageTime,
false,
false
));
);
entry.setOtherUserId(otherId);
if (archivedChatIds.contains(chat.getChat_id())) {
archivedChatList.add(entry);
chatList.add(entry);
} else {
activeChatList.add(entry);
chatList.add(entry);
}
}
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(
// chatList.add(new ChatEntry(
// group.getInternal_uuid(),
// group.getGroup_id(),
// group.getGroup_name(),
// group.getImage_url(),
// "group",
// last,
// isOwner,
// isAdmin
// ));
ChatEntry entry = new ChatEntry(
group.getInternal_uuid(),
group.getGroup_id(),
group.getGroup_name(),
@@ -125,7 +215,17 @@ public class ClientHandler implements Runnable {
last,
isOwner,
isAdmin
));
);
if (archivedChatIds.contains(group.getInternal_uuid())) {
archivedChatList.add(entry);
chatList.add(entry);
} else {
activeChatList.add(entry);
chatList.add(entry);
}
}
for (Channel channel : channels) {
@@ -133,7 +233,18 @@ public class ClientHandler implements Runnable {
boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), user.getInternal_uuid());
boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), user.getInternal_uuid());
chatList.add(new ChatEntry(
// chatList.add(new ChatEntry(
// channel.getInternal_uuid(),
// channel.getChannel_id(),
// channel.getChannel_name(),
// channel.getImage_url(),
// "channel",
// last,
// isOwner,
// isAdmin
// ));
ChatEntry entry = new ChatEntry(
channel.getInternal_uuid(),
channel.getChannel_id(),
channel.getChannel_name(),
@@ -142,8 +253,28 @@ public class ClientHandler implements Runnable {
last,
isOwner,
isAdmin
));
);
if (archivedChatIds.contains(channel.getInternal_uuid())) {
archivedChatList.add(entry);
chatList.add(entry);
} else {
activeChatList.add(entry);
chatList.add(entry);
}
}
activeChatList.sort((a, b) -> {
if (a.getLastMessageTime() == null) return 1;
if (b.getLastMessageTime() == null) return -1;
return b.getLastMessageTime().compareTo(a.getLastMessageTime());
});
archivedChatList.sort((a, b) -> {
if (a.getLastMessageTime() == null) return 1;
if (b.getLastMessageTime() == null) return -1;
return b.getLastMessageTime().compareTo(a.getLastMessageTime());
});
chatList.sort((a, b) -> {
@@ -154,6 +285,9 @@ public class ClientHandler implements Runnable {
JSONObject userData = JsonUtil.userToJson(user);
userData.put("chat_list", JsonUtil.chatListToJson(chatList));
userData.put("archived_chat_list", JsonUtil.chatListToJson(archivedChatList));
userData.put("active_chat_list", JsonUtil.chatListToJson(activeChatList));
userData.put("contact_list", contactList);
response = new ResponseModel("success", "Welcome " + user.getProfile_name(), userData);
}
break;
@@ -518,33 +652,85 @@ public class ClientHandler implements Runnable {
List<Contact> contacts = ContactDatabase.getContacts(currentUser.getInternal_uuid());
List<Group> groups = GroupDatabase.getGroupsByUser(currentUser.getInternal_uuid());
List<Channel> channels = ChannelDatabase.getChannelsByUser(currentUser.getInternal_uuid());
List<UUID> archivedChatIds = ArchivedChatDatabase.getArchivedChats(currentUser.getInternal_uuid());
List<ChatEntry> chatList = new ArrayList<>();
List<ChatEntry> archivedChatList = new ArrayList<>();
List<ChatEntry> activeChatList = new ArrayList<>();
for (Contact contact : contacts) {
User target = userDatabase.findByInternalUUID(contact.getContact_id());
if (target == null) continue;
LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(currentUser.getInternal_uuid(), target.getInternal_uuid(), "private");
chatList.add(new ChatEntry(
target.getInternal_uuid(),
target.getUser_id(),
target.getProfile_name(),
target.getImage_url(),
// for (Contact contact : contacts) {
// User target = userDatabase.findByInternalUUID(contact.getContact_id());
// if (target == null) continue;
//
// LocalDateTime last = MessageDatabase.getLastMessageTimeBetween(currentUser.getInternal_uuid(), target.getInternal_uuid(), "private");
//
// chatList.add(new ChatEntry(
// target.getInternal_uuid(),
// target.getUser_id(),
// target.getProfile_name(),
// target.getImage_url(),
// "private",
// last,
// false,
// false
// ));
// }
//
List<PrivateChat> privateChats = PrivateChatDatabase.findChatsOfUser(currentUser.getInternal_uuid());
for (PrivateChat chat : privateChats) {
UUID otherId = chat.getUser1_id().equals(currentUser.getInternal_uuid()) ?
chat.getUser2_id() : chat.getUser1_id();
User otherUser = userDatabase.findByInternalUUID(otherId);
if (otherUser == null) continue;
LocalDateTime lastMessageTime = MessageDatabase.getLastMessageTime(chat.getChat_id(), "private");
ChatEntry entry = new ChatEntry(
chat.getChat_id(),
otherUser.getUser_id(),
otherUser.getProfile_name(),
otherUser.getImage_url(),
"private",
last,
lastMessageTime,
false,
false
));
);
entry.setOtherUserId(otherId);
if (archivedChatIds.contains(chat.getChat_id())) {
archivedChatList.add(entry);
chatList.add(entry);
} else {
activeChatList.add(entry);
chatList.add(entry);
}
}
for (Group group : groups) {
LocalDateTime last = MessageDatabase.getLastMessageTime(group.getInternal_uuid(), "group");
boolean isOwner = GroupDatabase.isOwner(group.getInternal_uuid(), currentUser.getInternal_uuid());
boolean isAdmin = GroupDatabase.isAdmin(group.getInternal_uuid(), currentUser.getInternal_uuid());
chatList.add(new ChatEntry(
// chatList.add(new ChatEntry(
// group.getInternal_uuid(),
// group.getGroup_id(),
// group.getGroup_name(),
// group.getImage_url(),
// "group",
// last,
// isOwner,
// isAdmin
// ));
ChatEntry entry = new ChatEntry(
group.getInternal_uuid(),
group.getGroup_id(),
group.getGroup_name(),
@@ -553,7 +739,17 @@ public class ClientHandler implements Runnable {
last,
isOwner,
isAdmin
));
);
if (archivedChatIds.contains(group.getInternal_uuid())) {
archivedChatList.add(entry);
chatList.add(entry);
} else {
activeChatList.add(entry);
chatList.add(entry);
}
}
for (Channel channel : channels) {
@@ -561,7 +757,18 @@ public class ClientHandler implements Runnable {
boolean isOwner = ChannelDatabase.isOwner(channel.getInternal_uuid(), currentUser.getInternal_uuid());
boolean isAdmin = ChannelDatabase.isAdmin(channel.getInternal_uuid(), currentUser.getInternal_uuid());
chatList.add(new ChatEntry(
// chatList.add(new ChatEntry(
// channel.getInternal_uuid(),
// channel.getChannel_id(),
// channel.getChannel_name(),
// channel.getImage_url(),
// "channel",
// last,
// isOwner,
// isAdmin
// ));
ChatEntry entry = new ChatEntry(
channel.getInternal_uuid(),
channel.getChannel_id(),
channel.getChannel_name(),
@@ -570,17 +777,39 @@ public class ClientHandler implements Runnable {
last,
isOwner,
isAdmin
));
);
if (archivedChatIds.contains(channel.getInternal_uuid())) {
archivedChatList.add(entry);
chatList.add(entry);
} else {
activeChatList.add(entry);
chatList.add(entry);
}
}
activeChatList.sort((a, b) -> {
if (a.getLastMessageTime() == null) return 1;
if (b.getLastMessageTime() == null) return -1;
return b.getLastMessageTime().compareTo(a.getLastMessageTime());
});
archivedChatList.sort((a, b) -> {
if (a.getLastMessageTime() == null) return 1;
if (b.getLastMessageTime() == null) return -1;
return b.getLastMessageTime().compareTo(a.getLastMessageTime());
});
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));
data.put("archived_chat_list", JsonUtil.chatListToJson(archivedChatList));
data.put("active_chat_list", JsonUtil.chatListToJson(activeChatList));
response = new ResponseModel("success", "Chat list updated.", data);
break;
@@ -1029,13 +1258,17 @@ public class ClientHandler implements Runnable {
switch (receiverType) {
case "private" -> {
User otherUser = new userDatabase().findByInternalUUID(UUID.fromString(receiverId));
if (otherUser == null) {
response = new ResponseModel("error", "User not found.");
UUID chatId = UUID.fromString(receiverId);
List<UUID> members = PrivateChatDatabase.getMembers(chatId);
if (!members.contains(currentUser.getInternal_uuid())) {
response = new ResponseModel("error", "You're not a member of this private chat.");
break;
}
messages = MessageDatabase.privateChatHistory(currentUser.getInternal_uuid(), otherUser.getInternal_uuid());
messages = MessageDatabase.privateChatHistory(chatId);
}
case "group" -> {
Group group = GroupDatabase.findByInternalUUID(UUID.fromString(receiverId));
if (group == null) {
@@ -1070,6 +1303,9 @@ public class ClientHandler implements Runnable {
obj.put("receiver_type", m.getReceiver_type());
obj.put("content", m.getContent());
obj.put("send_at", m.getSend_at().toString());
User senderUser = userDatabase.findByInternalUUID(m.getSender_id());
String senderName = senderUser != null ? senderUser.getProfile_name() : "Unknown";
obj.put("sender_name", senderName);
messageArray.put(obj);
}
@@ -1196,26 +1432,33 @@ public class ClientHandler implements Runnable {
case "delete_private_chat" : {
case "delete_private_chat": {
if (currentUser == null) {
response = new ResponseModel("error", "Unauthorized. Please login first.");
break;
}
UUID targetId = UUID.fromString(requestJson.getString("target_id"));
// دریافت شناسه چت و نوع حذف (یک‌طرفه یا دوطرفه)
UUID targetId = UUID.fromString(requestJson.getString("chat_id"));
boolean both = requestJson.getBoolean("both");
//RealTime
// Real-Time Event Dispatch (اطلاع‌رسانی ریل تایم)
if (both) {
// حذف دوطرفه
RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid()));
RealTimeEventDispatcher.notifyChatDeleted("private", currentUser.getInternal_uuid(), List.of(targetId));
} else {
// حذف یک‌طرفه
RealTimeEventDispatcher.notifyChatDeleted("private", targetId, List.of(currentUser.getInternal_uuid()));
}
response = PrivateChatService.deletePrivateChat(currentUser.getInternal_uuid(), targetId, both);
// فراخوانی متد حذف چت
response = handleDeleteChat(requestJson);
break;
}
case "get_group_permissions": {
if (currentUser == null) {
response = new ResponseModel("error", "Unauthorized. Please login first.");
@@ -1707,6 +1950,85 @@ public class ClientHandler implements Runnable {
// break;
// }
//
case "archive_chat": {
if (currentUser == null) {
response = new ResponseModel("error", "Unauthorized. Please login first.");
break;
}
UUID chatId = UUID.fromString(requestJson.getString("chat_id"));
String chatType = requestJson.getString("chat_type");
boolean success = ArchivedChatDatabase.archiveChat(currentUser.getInternal_uuid(), chatId, chatType);
response = success
? new ResponseModel("success", "Chat archived successfully.")
: new ResponseModel("error", "Failed to archive chat.");
break;
}
case "unarchive_chat": {
if (currentUser == null) {
response = new ResponseModel("error", "Unauthorized. Please login first.");
break;
}
UUID chatId = UUID.fromString(requestJson.getString("chat_id"));
boolean success = ArchivedChatDatabase.unarchiveChat(currentUser.getInternal_uuid(), chatId);
response = success
? new ResponseModel("success", "Chat unarchived successfully.")
: new ResponseModel("error", "Failed to unarchive chat.");
break;
}
case "send_message" : {
response = handleSendMessage(requestJson);
}
break;
case "get_or_create_private_chat": {
if (currentUser == null) {
response = new ResponseModel("error", "Unauthorized. Please login first.");
break;
}
try {
UUID user1 = UUID.fromString(requestJson.getString("user1"));
UUID user2 = UUID.fromString(requestJson.getString("user2"));
UUID chatId = PrivateChatDatabase.getOrCreateChat(user1, user2);
JSONObject data = new JSONObject();
data.put("chat_id", chatId.toString());
response = new ResponseModel("success", "Private chat ID retrieved.", data);
} catch (Exception e) {
e.printStackTrace();
response = new ResponseModel("error", "Invalid data or internal error.");
}
break;
}
case "get_private_chat_target": {
UUID chatId = UUID.fromString(requestJson.getString("chat_id"));
userId = currentUser.getInternal_uuid();
UUID targetId = PrivateChatDatabase.getOtherUserInChat(chatId, userId);
if (targetId == null) {
response = new ResponseModel("error", "Could not find other user.");
} else {
JSONObject data = new JSONObject();
data.put("target_id", targetId.toString());
response = new ResponseModel("success", "Target fetched.", data);
}
break;
}
case "get_user_profile": {
// Get the current user's UUID
UUID user_UUID = this.currentUser.getInternal_uuid();
@@ -1844,4 +2166,129 @@ public class ClientHandler implements Runnable {
}
private ResponseModel handleSendMessage(JSONObject json) {
try {
if (currentUser == null)
return new ResponseModel("error", "Unauthorized. Please login first.");
UUID messageId = UUID.randomUUID();
UUID senderId = currentUser.getInternal_uuid();
String receiverType = json.getString("receiver_type");
UUID receiverId;
receiverId = UUID.fromString(json.getString("receiver_id"));
if(Objects.equals(receiverType, "private")){
PrivateChatDatabase.clearDeletedFlag(senderId, receiverId);
}
String content = json.optString("content", "");
String messageType = json.optString("message_type", "TEXT");
boolean inserted = MessageDatabase.insertMessage(messageId, senderId, receiverId, receiverType, content, messageType);
if (!inserted)
return new ResponseModel("error", "Failed to insert message.");
if (json.has("attachments")) {
JSONArray attachmentsArray = json.getJSONArray("attachments");
List<FileAttachment> attachments = new ArrayList<>();
for (int i = 0; i < attachmentsArray.length(); i++) {
JSONObject attJson = attachmentsArray.getJSONObject(i);
attachments.add(new FileAttachment(
attJson.getString("file_url"),
attJson.getString("file_type")
));
}
boolean attInserted = MessageDatabase.insertAttachments(messageId, attachments);
if (!attInserted)
return new ResponseModel("error", "Message inserted but failed to attach files.");
}
// Send real-time message
Message msg = new Message(messageId, senderId, receiverId, receiverType, content, messageType, LocalDateTime.now());
List<UUID> receivers = getReceiversForChat(receiverId, receiverType);
receivers.remove(senderId);
RealTimeEventDispatcher.sendNewMessage(msg, receivers);
// Update chat list (last_message_time)
JSONObject chatUpdate = new JSONObject();
chatUpdate.put("chat_id", receiverId.toString());
chatUpdate.put("chat_type", receiverType);
chatUpdate.put("last_message_time", LocalDateTime.now().toString());
JSONObject chatPayload = new JSONObject();
chatPayload.put("action", "chat_updated");
chatPayload.put("data", chatUpdate);
for (UUID receiver : receivers) {
RealTimeEventDispatcher.sendToUser(receiver, chatPayload);
}
JSONObject data = new JSONObject();
data.put("message_id", messageId.toString());
return new ResponseModel("success", "Message sent successfully.", data);
} catch (Exception e) {
e.printStackTrace();
return new ResponseModel("error", "Exception occurred while sending message.");
}
}
private List<UUID> getReceiversForChat(UUID receiverId, String receiverType) {
switch (receiverType) {
case "private":
return PrivateChatDatabase.getMembers(receiverId);
case "group":
return GroupDatabase.getMemberUUIDs(receiverId);
case "channel":
return ChannelDatabase.getSubscriberUUIDs(receiverId);
default:
return new ArrayList<>();
}
}
private ResponseModel handleDeleteChat(JSONObject json) {
try {
if (currentUser == null)
return new ResponseModel("error", "Unauthorized");
UUID chatId = UUID.fromString(json.getString("chat_id"));
boolean bothSides = json.optBoolean("both_sides", json.optBoolean("both", false));
PrivateChat chat = PrivateChatDatabase.findById(chatId);
if (chat == null) return new ResponseModel("error", "Chat not found.");
UUID self = currentUser.getInternal_uuid();
UUID other = chat.getUser1_id().equals(self) ? chat.getUser2_id() : chat.getUser1_id();
if (bothSides) {
PrivateChatDatabase.markBothDeleted(chatId);
MessageDatabase.markGloballyDeleted(chatId);
MessageDatabase.logDeletedMessagesFor(chatId, self);
MessageDatabase.logDeletedMessagesFor(chatId, other);
return new ResponseModel("success", "Chat deleted for both sides.");
} else {
if (chat.getUser1_id().equals(self))
PrivateChatDatabase.markUser1Deleted(chatId);
else
PrivateChatDatabase.markUser2Deleted(chatId);
MessageDatabase.logDeletedMessagesFor(chatId, self);
return new ResponseModel("success", "Chat deleted (one-sided).");
}
} catch (Exception e) {
e.printStackTrace();
return new ResponseModel("error", "Exception while deleting chat.");
}
}
}
@@ -3,6 +3,7 @@ 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.Database.userDatabase;
import org.to.telegramfinalproject.Models.Message;
import org.to.telegramfinalproject.Models.User;
@@ -151,7 +152,6 @@ public class RealTimeEventDispatcher {
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());
@@ -305,4 +305,30 @@ public class RealTimeEventDispatcher {
broadcastToUsers(affectedUsers, event);
}
public static void sendNewMessage(Message message, List<UUID> receivers) {
JSONObject payload = new JSONObject();
payload.put("action", "new_message");
JSONObject data = new JSONObject();
data.put("id", message.getMessage_id().toString());
data.put("sender_id", message.getSender_id().toString());
data.put("receiver_id", message.getReceiver_id().toString());
data.put("receiver_type", message.getReceiver_type());
data.put("content", message.getContent());
data.put("send_at", message.getSend_at().toString());
User sender = userDatabase.findByInternalUUID(message.getSender_id());
if (sender != null) {
data.put("sender_name", sender.getProfile_name());
}
payload.put("data", data);
for (UUID userId : receivers) {
sendToUser(userId, payload);
}
}
}
+36 -5
View File
@@ -27,24 +27,28 @@ CREATE TABLE IF NOT EXISTS messages (
receiver_id UUID NOT NULL ,
content TEXT,
message_type VARCHAR(20) DEFAULT 'TEXT' CHECK (message_type IN ('TEXT','IMAGE','FILE','VIDEO','AUDIO','STICKER','GIF')),
file_url TEXT, --(Bouns)
send_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20), -- SEND,DELIVERED,READ
reply_to_id UUID REFERENCES messages(message_id) ON DELETE SET NULL, --(Bouns)
is_edited BOOLEAN DEFAULT FALSE, --(Bonus)
original_message_id UUID REFERENCES messages(message_id)
ON DELETE SET NULL, --(Bonus)
edited_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
original_message_id UUID REFERENCES messages(message_id) ON DELETE SET NULL, --(Bonus)
forwarded_by UUID REFERENCES users(internal_uuid) ON DELETE SET NULL, --(Bouns)
forwarded_from UUID REFERENCES users(internal_uuid) ON DELETE SET NULL --(Bouns)
forwarded_from UUID REFERENCES users(internal_uuid) ON DELETE SET NULL, --(Bouns)
is_deleted_globally BOOLEAN DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS private_chat (
chat_id UUID PRIMARY KEY,
user1_id UUID REFERENCES users(internal_uuid) ON DELETE SET NULL,
user2_id UUID REFERENCES users(internal_uuid) ON DELETE SET NULL,
user1_deleted BOOLEAN DEFAULT FALSE,
user2_deleted BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_users UNIQUE (user1_id, user2_id)
);
CREATE TABLE IF NOT EXISTS groups (
internal_uuid UUID PRIMARY KEY,
group_id VARCHAR(70) UNIQUE,
@@ -67,7 +71,7 @@ CREATE TABLE group_members (
CREATE TABLE IF NOT EXISTS channels (
channel_id VARCHAR(70) UNIQUE,
internal_uuid UUID PRIMARY KEY,,
internal_uuid UUID PRIMARY KEY,
channel_name VARCHAR(100),
creator_id UUID REFERENCES users(internal_uuid),
image_url TEXT,
@@ -119,3 +123,30 @@ CREATE TABLE archived_chats (
);
CREATE TABLE deleted_messages (
message_id UUID REFERENCES messages(message_id) ON DELETE CASCADE,
user_id UUID REFERENCES users(internal_uuid) ON DELETE CASCADE,
deleted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (message_id, user_id)
);
CREATE TABLE IF NOT EXISTS message_reactions (
message_id UUID REFERENCES messages(message_id) ON DELETE CASCADE,
user_id UUID REFERENCES users(internal_uuid) ON DELETE CASCADE,
emoji TEXT NOT NULL,
reacted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (message_id, user_id)
);
--Multi attachments
CREATE TABLE IF NOT EXISTS message_attachments (
attachment_id UUID PRIMARY KEY,
message_id UUID REFERENCES messages(message_id) ON DELETE CASCADE,
file_url TEXT NOT NULL,
file_type VARCHAR(20) CHECK (file_type IN ('IMAGE','VIDEO','AUDIO','FILE','GIF','STICKER')),
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);