From 5825373f2cf879db36ea1b5e16c367f8e20202c2 Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Sun, 27 Jul 2025 22:47:11 +0330 Subject: [PATCH 01/21] add viewing user profile. --- .../Client/ActionHandler.java | 52 +++++- .../Client/SidebarAction.java | 12 ++ .../Client/SidebarHandler.java | 148 ++++++++++++++++++ .../Server/ClientHandler.java | 12 ++ .../Server/SidebarService.java | 57 +++++++ 5 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/to/telegramfinalproject/Client/SidebarAction.java create mode 100644 src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java create mode 100644 src/main/java/org/to/telegramfinalproject/Server/SidebarService.java diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 5d51ac6..6752ce1 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -703,7 +703,8 @@ public class ActionHandler { System.out.println("2. Search"); System.out.println("3. Create Channel"); System.out.println("4. Create group"); - System.out.println("5. Logout"); + System.out.println("5. Show sidebar menu"); + System.out.println("6. Logout"); System.out.print("Choose an option: "); String choice = scanner.nextLine(); @@ -716,7 +717,8 @@ public class ActionHandler { case "2" -> search(); case "3" -> createChannel(); case "4" -> createGroup(); - case "5" -> { + case "5" -> showSidebarMenu(); + case "6" -> { logout(); return; } @@ -2712,8 +2714,50 @@ public class ActionHandler { } } + private void showSidebarMenu() { + System.out.println("\n--- Sidebar Menu ---"); + System.out.println("1. View Profile"); + System.out.println("2. New Group"); + System.out.println("3. New Channel"); + System.out.println("4. View Contacts"); + System.out.println("5. Saved messages"); + System.out.println("6. Settings"); + System.out.println("7. Telegram features"); + System.out.println("8. Telegram Q&A"); + System.out.println("0. Back"); + System.out.println("Choose your action: (0 to 8)"); + // Handle invalid input + int action; + while (true) { + if (scanner.hasNextInt()) { + action = scanner.nextInt(); + scanner.nextLine(); // Clear newline -} - + if (action >= 0 && action <= 8) { + break; + } + System.out.println("Invalid input. Please enter a number between 0 and 8."); + } else { + scanner.nextLine(); + System.out.println("Invalid input. Try again."); + } + } + SidebarHandler sidebarHandler = new SidebarHandler(scanner); + switch (action) { + case 0 -> { + return; + } + case 1 -> sidebarHandler.handleSidebarAction(SidebarAction.MY_PROFILE); + case 2 -> sidebarHandler.handleSidebarAction(SidebarAction.NEW_GROUP); + case 3 -> sidebarHandler.handleSidebarAction(SidebarAction.NEW_CHANNEL); + case 4 -> sidebarHandler.handleSidebarAction(SidebarAction.CONTACTS); + case 5 -> sidebarHandler.handleSidebarAction(SidebarAction.SAVED_MESSAGES); + case 6 -> sidebarHandler.handleSidebarAction(SidebarAction.SETTINGS); + case 7 -> sidebarHandler.handleSidebarAction(SidebarAction.FEATURES); + case 8 -> sidebarHandler.handleSidebarAction(SidebarAction.Q_AND_A); + default -> System.out.println("Invalid action."); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarAction.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarAction.java new file mode 100644 index 0000000..8ad934a --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarAction.java @@ -0,0 +1,12 @@ +package org.to.telegramfinalproject.Client; + +public enum SidebarAction { + MY_PROFILE, + NEW_GROUP, + NEW_CHANNEL, + CONTACTS, + SAVED_MESSAGES, + SETTINGS, + FEATURES, + Q_AND_A +} diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java new file mode 100644 index 0000000..7edae86 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -0,0 +1,148 @@ +package org.to.telegramfinalproject.Client; + +import org.json.JSONObject; + +import java.util.Scanner; + +public class SidebarHandler { + private final Scanner scanner; + + public SidebarHandler(Scanner scanner) { + this.scanner = scanner; + } + + public void handleSidebarAction(SidebarAction action) { + switch (action) { + case MY_PROFILE: + openUserProfile(); + break; + case NEW_GROUP: + createNewGroup(); + break; + case NEW_CHANNEL: + createNewChannel(); + break; + case CONTACTS: + showContacts(); + break; + case SAVED_MESSAGES: + showSavedMessages(); + break; + case SETTINGS: + openSettings(); + break; + case FEATURES: + showTelegramFeatures(); + break; + case Q_AND_A: + showTelegramQA(); + break; + } + } + + private void openUserProfile() { + // Step 1: Request profile info from the server + JSONObject request = new JSONObject(); + request.put("action", "get_user_profile"); + + JSONObject response = ActionHandler.sendWithResponse(request); + if (response == null || !response.optString("status", "fail").equals("success")) { + System.out.println("Failed to load profile information."); + return; + } + + JSONObject profile = response.getJSONObject("data"); + + String profileName = profile.optString("profile_name", "NOT-AVAILABLE"); + String userId = profile.optString("user_id", "NOT-AVAILABLE"); + String status = profile.optString("status", "NOT-AVAILABLE"); + String bio = profile.optString("bio", "NOT-AVAILABLE"); + String profilePictureUrl = profile.optString("profile_picture_url", "NOT-AVAILABLE"); + + // Step 2: Show profile info + System.out.println("===== My Profile ====="); + System.out.println("Profile Picture URL : " + profilePictureUrl); + System.out.println("Profile Name : " + profileName); + System.out.println("User ID : " + userId); + System.out.println("Status : " + status); + System.out.println("Bio : " + bio); + System.out.println("======================"); + + // Step 3: Ask if user wants to edit anything + System.out.println("Do you want to edit any of these? (yes/no)"); + while (true) { + String choice = scanner.nextLine().trim().toLowerCase(); + if (choice.equals("no")) return; + if (!choice.equals("yes")) { + System.out.println("Invalid input. Please enter 'yes' or 'no'."); + continue; + } + + // Step 4: Show editable fields + System.out.println("Which field do you want to edit?"); + System.out.println("1. Profile Name"); + System.out.println("2. User ID"); + System.out.println("3. Bio"); + System.out.println("4. Profile Picture URL"); + System.out.println("0. Cancel"); + + String option = scanner.nextLine().trim(); + switch (option) { + case "1" -> editProfileName(); + case "2" -> editUserId(); + case "3" -> editBio(); + case "4" -> editProfilePictureUrl(); + case "0" -> { return; } + default -> System.out.println("Invalid choice. Try again."); + } + + // Re-fetch and re-display profile after editing + openUserProfile(); + return; + } + } + + private void editProfileName() { + + } + + private void editUserId() { + + } + + private void editBio() { + + } + + private void editProfilePictureUrl() { + + } + + private void createNewGroup() { + System.out.println("👥 Creating a new group..."); + } + + private void createNewChannel() { + System.out.println("📢 Creating a new channel..."); + } + + private void showContacts() { + System.out.println("📇 Showing contacts..."); + } + + private void showSavedMessages() { + System.out.println("💾 Showing saved messages..."); + } + + private void openSettings() { + System.out.println("⚙️ Opening settings..."); + } + + private void showTelegramFeatures() { + System.out.println("🌟 Showing Telegram features..."); + } + + private void showTelegramQA() { + System.out.println("❓ Showing Q&A..."); + } +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index e76ce67..6affe43 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1707,8 +1707,20 @@ public class ClientHandler implements Runnable { // break; // } // + case "get_user_profile": { + // Get the current user's user_id + String user_id = this.currentUser.getUser_id(); + // Get the profile JSON from SidebarService + JSONObject data = SidebarService.getUserProfile(user_id); + if (data == null) { + response = new ResponseModel("error", "Failed to retrieve user profile."); + } else { + response = new ResponseModel("success", "User profile retrieved successfully.", data); + } + break; + } default: response = new ResponseModel("error", "Unknown action: " + action); diff --git a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java new file mode 100644 index 0000000..eb67f6f --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java @@ -0,0 +1,57 @@ +package org.to.telegramfinalproject.Server; + +import org.json.JSONObject; +import org.to.telegramfinalproject.Database.userDatabase; +import org.to.telegramfinalproject.Models.User; + +public class SidebarService { + private static userDatabase userDB = new userDatabase(); + + // Returns current user's profile data (name, bio, status, etc.) + public static JSONObject getUserProfile(String userId) { + User user = userDB.findByUserId(userId); + + if (user == null) { + return null; + } + + JSONObject profile = new JSONObject(); + profile.put("user_id", user.getUser_id()); + profile.put("profile_name", user.getProfile_name()); + profile.put("bio", user.getBio() != null ? user.getBio() : ""); + profile.put("status", "ONLINE"); + profile.put("profile_picture_url", user.getImage_url()); + + return profile; + } + + // Updates one or more profile fields for the user + public static JSONObject updateUserProfile(String userId, JSONObject updateData) { + return null; + } + + // Changes the user's profile picture + public static JSONObject changeProfilePicture(String userId, byte[] imageData) { + return null; + } + + // Changes user-id + public static JSONObject updateUserId(String userId, String newUserId) { + return null; + } + + // (Optional) Changes username + public static JSONObject updateUserName(String userId, String newUserName) { + return null; + } + + // Changes user's profile name + public static JSONObject updateProfileName(String userId, String newProfileName) { + return null; + } + + // Changes or sets the user's date of birth + public static JSONObject updateDateOfBirth(String userId, String newDateOfBirth) { + return null; + } +} \ No newline at end of file From 5c6a63c6ae2d2e93b37b92433df83cf398bb5e59 Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Tue, 29 Jul 2025 13:00:19 +0330 Subject: [PATCH 02/21] Update init.sql --- src/main/resources/init.sql | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/resources/init.sql b/src/main/resources/init.sql index f07532e..981936b 100644 --- a/src/main/resources/init.sql +++ b/src/main/resources/init.sql @@ -27,15 +27,15 @@ 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) + is_edited BOOLEAN DEFAULT FALSE, --(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 ( @@ -119,3 +119,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 +); + + + From 2fb957d94121916dc6469132daa027ac1813f4ca Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Tue, 29 Jul 2025 13:35:00 +0330 Subject: [PATCH 03/21] added editing user profile. --- .../Client/SidebarAction.java | 2 +- .../Client/SidebarHandler.java | 109 ++++++++++++++- .../Database/userDatabase.java | 10 +- .../to/telegramfinalproject/Models/User.java | 4 +- .../Server/AuthService.java | 2 +- .../Server/ClientHandler.java | 56 ++++++++ .../Server/SidebarService.java | 132 +++++++++++++++--- 7 files changed, 290 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarAction.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarAction.java index 8ad934a..4a16145 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarAction.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarAction.java @@ -9,4 +9,4 @@ public enum SidebarAction { SETTINGS, FEATURES, Q_AND_A -} +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index 7edae86..c042c7a 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -1,5 +1,6 @@ package org.to.telegramfinalproject.Client; +import org.json.JSONException; import org.json.JSONObject; import java.util.Scanner; @@ -51,7 +52,13 @@ public class SidebarHandler { return; } - JSONObject profile = response.getJSONObject("data"); + JSONObject profile; + try { + profile = response.getJSONObject("data"); + } catch (JSONException e) { + System.out.println("Received malformed profile data from server."); + return; + } String profileName = profile.optString("profile_name", "NOT-AVAILABLE"); String userId = profile.optString("user_id", "NOT-AVAILABLE"); @@ -63,7 +70,7 @@ public class SidebarHandler { System.out.println("===== My Profile ====="); System.out.println("Profile Picture URL : " + profilePictureUrl); System.out.println("Profile Name : " + profileName); - System.out.println("User ID : " + userId); + System.out.println("User ID : @" + userId); System.out.println("Status : " + status); System.out.println("Bio : " + bio); System.out.println("======================"); @@ -103,19 +110,117 @@ public class SidebarHandler { } private void editProfileName() { + System.out.println("Enter your new profile name:"); + String newProfileName = scanner.nextLine().trim(); + // Step 1: Validate input + while (newProfileName.trim().isEmpty()) { + System.out.println("Profile name cannot be empty. Enter another name."); + newProfileName = scanner.nextLine().trim(); + } + + // Step 2: Create request + JSONObject request = new JSONObject(); + request.put("action", "edit_profile_name"); + request.put("new_profile_name", newProfileName); + + // Step 3: Send request and receive response + JSONObject response = ActionHandler.sendWithResponse(request); + + // Step 4: Handle response + if (response == null || !response.optString("status", "fail").equals("success")) { + System.out.println("Failed to update profile name."); + } else { + System.out.println("Profile name updated successfully!"); + } } private void editUserId() { + while (true) { + System.out.println("Enter your new user ID: "); + String newUserId = scanner.nextLine().trim(); + if (newUserId.isEmpty()) { + System.out.println("User ID cannot be empty."); + continue; + } + + if (newUserId.contains(" ")) { + System.out.println("User ID cannot contain spaces."); + continue; + } + + if (!newUserId.matches("^[a-zA-Z0-9_]+$")) { + System.out.println("User ID can only contain letters, digits, and underscores."); + continue; + } + + // Send to server + JSONObject request = new JSONObject(); + request.put("action", "edit_user_id"); + request.put("new_user_id", newUserId); + JSONObject response = ActionHandler.sendWithResponse(request); + + if (response.getString("status").equals("success")) { + System.out.println("User ID updated successfully."); + break; + } else { + // Server-side error message (e.g., ID already exists) + System.out.println(response.getString("message")); + } + } } private void editBio() { + System.out.println("Enter your new bio:"); + String newBio = scanner.nextLine().trim(); + // Limit the bio length + if (newBio.length() > 70) { + System.out.println("Bio cannot be more than 70 characters."); + return; + } + + JSONObject request = new JSONObject(); + request.put("action", "edit_bio"); + request.put("new_bio", newBio); + + JSONObject response = ActionHandler.sendWithResponse(request); + + if (response.getString("status").equals("success")) { + System.out.println("Bio updated successfully."); + } else { + System.out.println(response.getString("message")); + } } private void editProfilePictureUrl() { + while (true) { + System.out.println("Enter new profile picture URL (or leave empty to remove):"); + String newImageUrl = scanner.nextLine().trim(); + if (newImageUrl.contains(" ")) { + System.out.println("URL cannot contain spaces."); + continue; + } + + if (!newImageUrl.isEmpty() && !newImageUrl.matches("^(http|https)://.*$")) { + System.out.println("Invalid URL format. Please enter a valid HTTP/HTTPS link."); + continue; + } + + JSONObject request = new JSONObject(); + request.put("action", "edit_profile_picture"); + request.put("new_image_url", newImageUrl); + JSONObject response = ActionHandler.sendWithResponse(request); + + if (response.getString("status").equals("success")) { + System.out.println("Bio updated successfully."); + } else { + System.out.println(response.getString("message")); + } + break; + } } private void createNewGroup() { diff --git a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java index 6064500..dc9d7dc 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java @@ -152,7 +152,7 @@ public class userDatabase { } public boolean save(User user) { - String query = "INSERT INTO users (user_id, internal_uuid, username, password, profile_name) VALUES (?, ?, ?, ?, ?)"; + String query = "INSERT INTO users (user_id, internal_uuid, username, password, profile_name, bio, image_url) VALUES (?, ?, ?, ?, ?, ?, ?)"; try { boolean var5; @@ -165,6 +165,8 @@ public class userDatabase { stmt.setString(3, user.getUsername()); stmt.setString(4, user.getPassword()); stmt.setString(5, user.getProfile_name()); + stmt.setString(6, user.getBio()); + stmt.setString(7, user.getImage_url()); var5 = stmt.executeUpdate() > 0; } @@ -223,7 +225,7 @@ public class userDatabase { } private User extractUser(ResultSet rs) throws SQLException { - return new User(rs.getString("user_id"), UUID.fromString(rs.getString("internal_uuid")), rs.getString("username"), rs.getString("password"), rs.getString("profile_name")); + return new User(rs.getString("user_id"), UUID.fromString(rs.getString("internal_uuid")), rs.getString("username"), rs.getString("password"), rs.getString("profile_name"), rs.getString("bio"), rs.getString("image_url")); } public boolean deleteByUUID(UUID uuid) { @@ -288,7 +290,9 @@ public class userDatabase { UUID.fromString(rs.getString("internal_uuid")), rs.getString("username"), rs.getString("password"), - rs.getString("profile_name") + rs.getString("profile_name"), + rs.getString("bio"), + rs.getString("image_url") ); user.setBio(rs.getString("bio")); diff --git a/src/main/java/org/to/telegramfinalproject/Models/User.java b/src/main/java/org/to/telegramfinalproject/Models/User.java index 73ac635..a8d9e29 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/User.java +++ b/src/main/java/org/to/telegramfinalproject/Models/User.java @@ -22,12 +22,14 @@ public class User { private List unreadMessages; private List chatList; - public User(String user_id, UUID internal_uuid, String username, String password, String profile_name) { + public User(String user_id, UUID internal_uuid, String username, String password, String profile_name, String bio, String image_url) { this.user_id = user_id; this.internal_uuid = internal_uuid; this.username = username; this.password = password; this.profile_name = profile_name; + this.bio = bio; + this.image_url = image_url; } public void setUser_id(String user_id) { diff --git a/src/main/java/org/to/telegramfinalproject/Server/AuthService.java b/src/main/java/org/to/telegramfinalproject/Server/AuthService.java index 1534c46..f50da87 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/AuthService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/AuthService.java @@ -20,7 +20,7 @@ public class AuthService { } else { UUID uuid = UUID.randomUUID(); password = PasswordHashing.hash(password); - User user = new User(user_id, uuid, username, password, profile_name); + User user = new User(user_id, uuid, username, password, profile_name, "", ""); return this.userDb.save(user); } } else { diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 6affe43..50769b4 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1722,6 +1722,62 @@ public class ClientHandler implements Runnable { break; } + case "edit_profile_name": { + // Validate input + if (!requestJson.has("new_profile_name")) { + response = new ResponseModel("error", "Missing new profile name."); + break; + } + + String user_id = this.currentUser.getUser_id(); + String newProfileName = requestJson.getString("new_profile_name"); + + response = SidebarService.updateProfileName(user_id, newProfileName); + break; + } + + case "edit_user_id": { + // Validate input + if (!requestJson.has("new_user-id")) { + response = new ResponseModel("error", "Missing new user ID."); + break; + } + + String currentUserId = this.currentUser.getUser_id(); + String newUserId = requestJson.getString("new_user_id"); + + response = SidebarService.updateUserId(currentUserId, newUserId); + break; + } + + case "edit_bio": { + // Validate input + if (!requestJson.has("new_bio")) { + response = new ResponseModel("error", "Missing new bio."); + break; + } + + String currentUserId = this.currentUser.getUser_id(); + String newBio = requestJson.getString("new_bio").trim(); + + response = SidebarService.updateBio(currentUserId, newBio); + break; + } + + case "edit_profile_picture": { + // Validate input + if (!requestJson.has("new_image_url")) { + response = new ResponseModel("error", "Missing new image url."); + break; + } + + String currentUserId = this.currentUser.getUser_id(); + String newImageUrl = requestJson.getString("new_image_url").trim(); + + response = SidebarService.updateProfilePicture(currentUserId, newImageUrl); + break; + } + default: response = new ResponseModel("error", "Unknown action: " + action); } diff --git a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java index eb67f6f..920e195 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java @@ -2,8 +2,11 @@ package org.to.telegramfinalproject.Server; import org.json.JSONObject; import org.to.telegramfinalproject.Database.userDatabase; +import org.to.telegramfinalproject.Models.ResponseModel; import org.to.telegramfinalproject.Models.User; +import java.util.Objects; + public class SidebarService { private static userDatabase userDB = new userDatabase(); @@ -25,33 +28,128 @@ public class SidebarService { return profile; } - // Updates one or more profile fields for the user - public static JSONObject updateUserProfile(String userId, JSONObject updateData) { - return null; + // Changes the user's profile picture + public static ResponseModel updateProfilePicture(String userId, String newImageUrl) { + User user = userDB.findByUserId(userId); + if (user == null) { + return new ResponseModel("error", "User not found."); + } + + String currentUrl = user.getImage_url(); + if (Objects.equals(currentUrl, newImageUrl)) { + return new ResponseModel("error", "No changes made. Profile picture is the same."); + } + + user.setImage_url(newImageUrl); // Can be null + boolean success = userDB.updateByUUID(user.getInternal_uuid(), user); + + if (success) { + return new ResponseModel("success", "Profile picture updated successfully."); + } else { + user.setImage_url(currentUrl); + return new ResponseModel("error", "Failed to update profile picture."); + } } - // Changes the user's profile picture - public static JSONObject changeProfilePicture(String userId, byte[] imageData) { - return null; + // Changes user's bio + public static ResponseModel updateBio(String userId, String newBio) { + if (newBio.length() > 70) { + return new ResponseModel("error", "Bio is too long."); + } + + User user = userDB.findByUserId(userId); + if (user == null) { + return new ResponseModel("error", "User not found."); + } + + String currentBio = user.getBio(); + if (Objects.equals(currentBio, newBio)) { + return new ResponseModel("error", "No changes made. Bio is the same."); + } + + user.setBio(newBio); + boolean success = userDB.updateByUUID(user.getInternal_uuid(), user); + + if (success) { + return new ResponseModel("success", "Bio updated successfully."); + } else { + user.setBio(currentBio); + return new ResponseModel("error", "Failed to update bio."); + } } // Changes user-id - public static JSONObject updateUserId(String userId, String newUserId) { - return null; - } + public static ResponseModel updateUserId(String currentUserId, String newUserId) { + if (newUserId == null || newUserId.trim().isEmpty()) { + return new ResponseModel("error", "User ID cannot be empty."); + } - // (Optional) Changes username - public static JSONObject updateUserName(String userId, String newUserName) { - return null; + newUserId = newUserId.trim(); + + if (newUserId.contains(" ")) { + return new ResponseModel("error", "User ID cannot contain spaces."); + } + + if (!newUserId.matches("^[a-zA-Z0-9_]+$")) { + return new ResponseModel("error", "User ID can only contain letters, digits, and underscores."); + } + + if (userDB.findByUserId(newUserId) != null) { + return new ResponseModel("error", "This user ID is already taken."); + } + + User user = userDB.findByUserId(currentUserId); + if (user == null) { + return new ResponseModel("error", "User not found."); + } + + if (Objects.equals(currentUserId, newUserId)) { + return new ResponseModel("error", "No changes made. User ID is the same."); + } + + user.setUser_id(newUserId); + + boolean saved = userDB.updateByUUID(user.getInternal_uuid(), user); + if (saved) { + return new ResponseModel("success", "User ID updated successfully."); + } else { + user.setUser_id(currentUserId); + return new ResponseModel("error", "Failed to update user ID due to server error."); + } } // Changes user's profile name - public static JSONObject updateProfileName(String userId, String newProfileName) { - return null; + public static ResponseModel updateProfileName(String userId, String newProfileName) { + if (newProfileName == null || newProfileName.trim().isEmpty()) { + return new ResponseModel("error", "Invalid input."); // Invalid input (empty or just spaces) + } + + // Fetch the user from database + User user = userDB.findByUserId(userId); + if (user == null) { + return new ResponseModel("error", "User not found."); // User doesn't exist + } + + String oldName = user.getProfile_name(); + if (Objects.equals(oldName, newProfileName)) { + return new ResponseModel("error", "No changes made. Profile name is the same."); + } + + // Only set the profile name *after* successful DB update + user.setProfile_name(newProfileName); + + boolean saved = userDB.updateByUUID(user.getInternal_uuid(), user); + + if (saved) { + return new ResponseModel("success", "Profile name updated successfully."); + } else { + user.setProfile_name(oldName); + return new ResponseModel("error", "Failed to update profile name."); + } } - // Changes or sets the user's date of birth - public static JSONObject updateDateOfBirth(String userId, String newDateOfBirth) { - return null; + // Changes username + public static boolean updateUserName(String userId, String newUserName) { + return false; } } \ No newline at end of file From 648a128a0cfebdbcdbf4ffae5c0f47c5ad3eff97 Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Tue, 29 Jul 2025 23:07:55 +0330 Subject: [PATCH 04/21] added creating new group and channel on sidebar menu. --- .../org/to/telegramfinalproject/Client/ActionHandler.java | 2 +- .../to/telegramfinalproject/Client/SidebarHandler.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 6752ce1..ed59769 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -2744,7 +2744,7 @@ public class ActionHandler { } } - SidebarHandler sidebarHandler = new SidebarHandler(scanner); + SidebarHandler sidebarHandler = new SidebarHandler(scanner, this); switch (action) { case 0 -> { return; diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index c042c7a..04ffb0f 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -7,9 +7,11 @@ import java.util.Scanner; public class SidebarHandler { private final Scanner scanner; + private final ActionHandler actionHandler; - public SidebarHandler(Scanner scanner) { + public SidebarHandler(Scanner scanner, ActionHandler actionHandler) { this.scanner = scanner; + this.actionHandler = actionHandler; } public void handleSidebarAction(SidebarAction action) { @@ -224,11 +226,11 @@ public class SidebarHandler { } private void createNewGroup() { - System.out.println("👥 Creating a new group..."); + actionHandler.createGroup(); } private void createNewChannel() { - System.out.println("📢 Creating a new channel..."); + actionHandler.createChannel(); } private void showContacts() { From a9b5e8abdff61178533ce46cf39637b7c797fbf7 Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Tue, 29 Jul 2025 23:34:12 +0330 Subject: [PATCH 05/21] editing user profile updated. --- .../Server/ClientHandler.java | 24 +++++++-------- .../Server/SidebarService.java | 30 ++++++++++--------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 50769b4..99bd97c 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1708,11 +1708,11 @@ public class ClientHandler implements Runnable { // } // case "get_user_profile": { - // Get the current user's user_id - String user_id = this.currentUser.getUser_id(); + // Get the current user's UUID + UUID user_UUID = this.currentUser.getInternal_uuid(); // Get the profile JSON from SidebarService - JSONObject data = SidebarService.getUserProfile(user_id); + JSONObject data = SidebarService.getUserProfile(user_UUID); if (data == null) { response = new ResponseModel("error", "Failed to retrieve user profile."); @@ -1729,24 +1729,24 @@ public class ClientHandler implements Runnable { break; } - String user_id = this.currentUser.getUser_id(); + UUID user_UUID = this.currentUser.getInternal_uuid(); String newProfileName = requestJson.getString("new_profile_name"); - response = SidebarService.updateProfileName(user_id, newProfileName); + response = SidebarService.updateProfileName(user_UUID, newProfileName); break; } case "edit_user_id": { // Validate input - if (!requestJson.has("new_user-id")) { + if (!requestJson.has("new_user_id")) { response = new ResponseModel("error", "Missing new user ID."); break; } - String currentUserId = this.currentUser.getUser_id(); + UUID user_UUID = this.currentUser.getInternal_uuid(); String newUserId = requestJson.getString("new_user_id"); - response = SidebarService.updateUserId(currentUserId, newUserId); + response = SidebarService.updateUserId(user_UUID, newUserId); break; } @@ -1757,10 +1757,10 @@ public class ClientHandler implements Runnable { break; } - String currentUserId = this.currentUser.getUser_id(); + UUID user_UUID = this.currentUser.getInternal_uuid(); String newBio = requestJson.getString("new_bio").trim(); - response = SidebarService.updateBio(currentUserId, newBio); + response = SidebarService.updateBio(user_UUID, newBio); break; } @@ -1771,10 +1771,10 @@ public class ClientHandler implements Runnable { break; } - String currentUserId = this.currentUser.getUser_id(); + UUID user_UUID = this.currentUser.getInternal_uuid(); String newImageUrl = requestJson.getString("new_image_url").trim(); - response = SidebarService.updateProfilePicture(currentUserId, newImageUrl); + response = SidebarService.updateProfilePicture(user_UUID, newImageUrl); break; } diff --git a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java index 920e195..e81f24b 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java @@ -6,13 +6,14 @@ import org.to.telegramfinalproject.Models.ResponseModel; import org.to.telegramfinalproject.Models.User; import java.util.Objects; +import java.util.UUID; public class SidebarService { private static userDatabase userDB = new userDatabase(); // Returns current user's profile data (name, bio, status, etc.) - public static JSONObject getUserProfile(String userId) { - User user = userDB.findByUserId(userId); + public static JSONObject getUserProfile(UUID userUUID) { + User user = userDB.findByInternalUUID(userUUID); if (user == null) { return null; @@ -29,8 +30,8 @@ public class SidebarService { } // Changes the user's profile picture - public static ResponseModel updateProfilePicture(String userId, String newImageUrl) { - User user = userDB.findByUserId(userId); + public static ResponseModel updateProfilePicture(UUID userUUID, String newImageUrl) { + User user = userDB.findByInternalUUID(userUUID); if (user == null) { return new ResponseModel("error", "User not found."); } @@ -41,7 +42,7 @@ public class SidebarService { } user.setImage_url(newImageUrl); // Can be null - boolean success = userDB.updateByUUID(user.getInternal_uuid(), user); + boolean success = userDB.updateByUUID(userUUID, user); if (success) { return new ResponseModel("success", "Profile picture updated successfully."); @@ -52,12 +53,12 @@ public class SidebarService { } // Changes user's bio - public static ResponseModel updateBio(String userId, String newBio) { + public static ResponseModel updateBio(UUID userUUID, String newBio) { if (newBio.length() > 70) { return new ResponseModel("error", "Bio is too long."); } - User user = userDB.findByUserId(userId); + User user = userDB.findByInternalUUID(userUUID); if (user == null) { return new ResponseModel("error", "User not found."); } @@ -68,7 +69,7 @@ public class SidebarService { } user.setBio(newBio); - boolean success = userDB.updateByUUID(user.getInternal_uuid(), user); + boolean success = userDB.updateByUUID(userUUID, user); if (success) { return new ResponseModel("success", "Bio updated successfully."); @@ -79,7 +80,7 @@ public class SidebarService { } // Changes user-id - public static ResponseModel updateUserId(String currentUserId, String newUserId) { + public static ResponseModel updateUserId(UUID userUUID, String newUserId) { if (newUserId == null || newUserId.trim().isEmpty()) { return new ResponseModel("error", "User ID cannot be empty."); } @@ -98,18 +99,19 @@ public class SidebarService { return new ResponseModel("error", "This user ID is already taken."); } - User user = userDB.findByUserId(currentUserId); + User user = userDB.findByInternalUUID(userUUID); if (user == null) { return new ResponseModel("error", "User not found."); } + String currentUserId = user.getUser_id(); if (Objects.equals(currentUserId, newUserId)) { return new ResponseModel("error", "No changes made. User ID is the same."); } user.setUser_id(newUserId); - boolean saved = userDB.updateByUUID(user.getInternal_uuid(), user); + boolean saved = userDB.updateByUUID(userUUID, user); if (saved) { return new ResponseModel("success", "User ID updated successfully."); } else { @@ -119,13 +121,13 @@ public class SidebarService { } // Changes user's profile name - public static ResponseModel updateProfileName(String userId, String newProfileName) { + public static ResponseModel updateProfileName(UUID userUUID, String newProfileName) { if (newProfileName == null || newProfileName.trim().isEmpty()) { return new ResponseModel("error", "Invalid input."); // Invalid input (empty or just spaces) } // Fetch the user from database - User user = userDB.findByUserId(userId); + User user = userDB.findByInternalUUID(userUUID); if (user == null) { return new ResponseModel("error", "User not found."); // User doesn't exist } @@ -138,7 +140,7 @@ public class SidebarService { // Only set the profile name *after* successful DB update user.setProfile_name(newProfileName); - boolean saved = userDB.updateByUUID(user.getInternal_uuid(), user); + boolean saved = userDB.updateByUUID(userUUID, user); if (saved) { return new ResponseModel("success", "Profile name updated successfully."); From 3186e6ee24506881a060cb383eb3307319c2264b Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Wed, 30 Jul 2025 00:05:35 +0330 Subject: [PATCH 06/21] init.sql updated. --- src/main/resources/init.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/init.sql b/src/main/resources/init.sql index f07532e..58c0571 100644 --- a/src/main/resources/init.sql +++ b/src/main/resources/init.sql @@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS private_chat ( user1_id UUID REFERENCES users(internal_uuid) ON DELETE SET NULL, user2_id UUID REFERENCES users(internal_uuid) ON DELETE SET NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + type ENUM('normal', 'saved') DEFAULT 'normal' -- Saved message chat or a normal chat CONSTRAINT unique_users UNIQUE (user1_id, user2_id) ); CREATE TABLE IF NOT EXISTS groups ( From 46f036cb31ff74bea3531e92b45a07c23efc564c Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Wed, 30 Jul 2025 10:45:35 +0330 Subject: [PATCH 07/21] Revert "init.sql updated." This reverts commit 3186e6ee24506881a060cb383eb3307319c2264b. --- src/main/resources/init.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/init.sql b/src/main/resources/init.sql index 58c0571..f07532e 100644 --- a/src/main/resources/init.sql +++ b/src/main/resources/init.sql @@ -43,7 +43,6 @@ CREATE TABLE IF NOT EXISTS private_chat ( user1_id UUID REFERENCES users(internal_uuid) ON DELETE SET NULL, user2_id UUID REFERENCES users(internal_uuid) ON DELETE SET NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - type ENUM('normal', 'saved') DEFAULT 'normal' -- Saved message chat or a normal chat CONSTRAINT unique_users UNIQUE (user1_id, user2_id) ); CREATE TABLE IF NOT EXISTS groups ( From 6b320b63599b1a54e61a1918a2c7b72d31c6d73f Mon Sep 17 00:00:00 2001 From: Partow Roshani Date: Fri, 1 Aug 2025 16:08:12 +0330 Subject: [PATCH 08/21] Update init.sql --- src/main/resources/init.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/init.sql b/src/main/resources/init.sql index 981936b..5e5c7a4 100644 --- a/src/main/resources/init.sql +++ b/src/main/resources/init.sql @@ -42,9 +42,13 @@ 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, From 9887b2af4226ce38a586b510b6dafa29baec3d40 Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Sat, 2 Aug 2025 14:47:26 +0330 Subject: [PATCH 09/21] Saved messages chat implemented. --- .../Client/SidebarHandler.java | 159 +++++++++++++++++- .../Database/PrivateChatDatabase.java | 38 +++++ .../telegramfinalproject/Models/Message.java | 2 +- .../Server/ClientHandler.java | 11 ++ .../Server/SidebarService.java | 81 +++++++++ 5 files changed, 282 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index 04ffb0f..258d9f5 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -1,17 +1,29 @@ package org.to.telegramfinalproject.Client; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.to.telegramfinalproject.Models.Message; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Scanner; +import java.util.UUID; + +import static org.to.telegramfinalproject.Client.ActionHandler.sendWithResponse; public class SidebarHandler { private final Scanner scanner; private final ActionHandler actionHandler; + private final String userUUID; public SidebarHandler(Scanner scanner, ActionHandler actionHandler) { this.scanner = scanner; this.actionHandler = actionHandler; + this.userUUID = Session.getUserUUID(); + } public void handleSidebarAction(SidebarAction action) { @@ -29,7 +41,7 @@ public class SidebarHandler { showContacts(); break; case SAVED_MESSAGES: - showSavedMessages(); + getSavedMessagesData(userUUID); break; case SETTINGS: openSettings(); @@ -48,7 +60,7 @@ public class SidebarHandler { JSONObject request = new JSONObject(); request.put("action", "get_user_profile"); - JSONObject response = ActionHandler.sendWithResponse(request); + JSONObject response = sendWithResponse(request); if (response == null || !response.optString("status", "fail").equals("success")) { System.out.println("Failed to load profile information."); return; @@ -127,7 +139,7 @@ public class SidebarHandler { request.put("new_profile_name", newProfileName); // Step 3: Send request and receive response - JSONObject response = ActionHandler.sendWithResponse(request); + JSONObject response = sendWithResponse(request); // Step 4: Handle response if (response == null || !response.optString("status", "fail").equals("success")) { @@ -161,7 +173,7 @@ public class SidebarHandler { JSONObject request = new JSONObject(); request.put("action", "edit_user_id"); request.put("new_user_id", newUserId); - JSONObject response = ActionHandler.sendWithResponse(request); + JSONObject response = sendWithResponse(request); if (response.getString("status").equals("success")) { System.out.println("User ID updated successfully."); @@ -187,7 +199,7 @@ public class SidebarHandler { request.put("action", "edit_bio"); request.put("new_bio", newBio); - JSONObject response = ActionHandler.sendWithResponse(request); + JSONObject response = sendWithResponse(request); if (response.getString("status").equals("success")) { System.out.println("Bio updated successfully."); @@ -214,7 +226,7 @@ public class SidebarHandler { JSONObject request = new JSONObject(); request.put("action", "edit_profile_picture"); request.put("new_image_url", newImageUrl); - JSONObject response = ActionHandler.sendWithResponse(request); + JSONObject response = sendWithResponse(request); if (response.getString("status").equals("success")) { System.out.println("Bio updated successfully."); @@ -237,8 +249,139 @@ public class SidebarHandler { System.out.println("📇 Showing contacts..."); } - private void showSavedMessages() { - System.out.println("💾 Showing saved messages..."); + public void getSavedMessagesData(String userId) { + try { + // Step 1: Create the request + JSONObject request = new JSONObject(); + request.put("action", "get_saved_messages"); + request.put("user_id", userId); + + // Step 2: Send request and wait for response + JSONObject response = ActionHandler.sendWithResponse(request); + + // Step 3: Check the response + if (!response.optString("status", "fail").equals("success")) { + System.out.println("Failed to open Saved Messages chat: " + response.optString("message", "Unknown error")); + return; + } + + // Step 4: Extract "data" object + JSONObject data = response.getJSONObject("data"); + UUID chatId = UUID.fromString(data.getString("chat_id")); + JSONArray messagesArray = data.getJSONArray("messages"); + + // Step 5: Parse messages + List messages = new ArrayList<>(); + if (!messagesArray.isEmpty()) { + for (int i = 0; i < messagesArray.length(); i++) { + JSONObject msgJson = messagesArray.getJSONObject(i); + + // Safely extract optional UUIDs + UUID replyToId = null; + String replyToIdStr = msgJson.optString("reply_to_id", null); + if (replyToIdStr != null && !replyToIdStr.equals("null")) { + replyToId = UUID.fromString(replyToIdStr); + } + + UUID originalMessageId = null; + String originalMessageIdStr = msgJson.optString("original_message_id", null); + if (originalMessageIdStr != null && !originalMessageIdStr.equals("null")) { + originalMessageId = UUID.fromString(originalMessageIdStr); + } + + UUID forwardedBy = null; + String forwardedByStr = msgJson.optString("forwarded_by", null); + if (forwardedByStr != null && !forwardedByStr.equals("null")) { + forwardedBy = UUID.fromString(forwardedByStr); + } + + UUID forwardedFrom = null; + String forwardedFromStr = msgJson.optString("forwarded_from", null); + if (forwardedFromStr != null && !forwardedFromStr.equals("null")) { + forwardedFrom = UUID.fromString(forwardedFromStr); + } + + Message msg = new Message( + UUID.fromString(msgJson.getString("message_id")), + UUID.fromString(msgJson.getString("sender_id")), + msgJson.getString("receiver_type"), + UUID.fromString(msgJson.getString("receiver_id")), + msgJson.getString("content"), + msgJson.getString("message_type"), + msgJson.optString("file_url", null), + LocalDateTime.parse(msgJson.getString("send_at").replace(" ", "T")), + msgJson.getString("status"), + replyToId, + msgJson.getBoolean("is_edited"), + originalMessageId, + forwardedBy, + forwardedFrom + ); + + messages.add(msg); + } + } + + // Step 6: Show chat + showSavedMessages(chatId, messages); + + } catch (Exception e) { + System.out.println("An error occurred while retrieving Saved Messages."); + e.printStackTrace(); + } + } + + private void showSavedMessages(UUID chatId, List messages) { + Scanner scanner = new Scanner(System.in); + + System.out.println("==== Saved Messages ===="); + + // Show previous messages + if (messages.isEmpty()) { + System.out.println("No messages yet."); + } else { + for (Message msg : messages) { + System.out.println("[" + msg.getSend_at() + "] " + msg.getContent()); + } + } + + System.out.println("\n(Type your message below, or type 0 to exit)"); + + while (true) { + System.out.print("You: "); + String content = scanner.nextLine().trim(); + + if (content.equals("0")) { + System.out.println("Exiting Saved Messages."); + break; + } + + // Prepare the request JSON + JSONObject request = new JSONObject(); + request.put("action", "send_message"); + request.put("message_id", UUID.randomUUID().toString()); + request.put("sender_id", userUUID); + request.put("receiver_type", "private"); + request.put("receiver_id", userUUID); // saved messages = to yourself + request.put("content", content); + request.put("message_type", "TEXT"); + request.put("file_url", JSONObject.NULL); + request.put("status", "READ"); + request.put("reply_to_id", JSONObject.NULL); + request.put("is_edited", false); + request.put("original_message_id", JSONObject.NULL); + request.put("forwarded_by", JSONObject.NULL); + request.put("forwarded_from", JSONObject.NULL); + + // Send the message and wait for response + JSONObject response = ActionHandler.sendWithResponse(request); + + if (!response.optString("status", "fail").equals("success")) { + System.out.println("Failed to send message: " + response.optString("message", "Unknown error")); + } else { + System.out.println("Message sent."); + } + } } private void openSettings() { diff --git a/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java new file mode 100644 index 0000000..2114f4a --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/Database/PrivateChatDatabase.java @@ -0,0 +1,38 @@ +package org.to.telegramfinalproject.Database; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.UUID; + +public class PrivateChatDatabase { + + 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()) { + PreparedStatement stmt = conn.prepareStatement(query); + stmt.setObject(1, userId); + stmt.setObject(2, userId); // Saved Messages is self-chat + + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + return (UUID) rs.getObject("chat_id"); // Chat already exists + } else { + // Create a new chat + UUID chatId = UUID.randomUUID(); + String insert = "INSERT INTO private_chat (chat_id, user1_id, user2_id) VALUES (?, ?, ?)"; + try (PreparedStatement insertStmt = conn.prepareStatement(insert)) { + insertStmt.setObject(1, chatId); + insertStmt.setObject(2, userId); + insertStmt.setObject(3, userId); + insertStmt.executeUpdate(); + return chatId; + } + } + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/Models/Message.java b/src/main/java/org/to/telegramfinalproject/Models/Message.java index eb7688a..00e5764 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/Message.java +++ b/src/main/java/org/to/telegramfinalproject/Models/Message.java @@ -4,7 +4,7 @@ import java.time.LocalDateTime; import java.util.UUID; public class Message { - private UUID message_id; + private final UUID message_id; private UUID sender_id; private String receiver_type; // private, group, channel private UUID receiver_id; diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 99bd97c..32f7274 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -1778,6 +1778,17 @@ public class ClientHandler implements Runnable { break; } + case "get_saved_messages": { + UUID user_Id = UUID.fromString(requestJson.getString("user_id")); + response = SidebarService.handleGetSavedMessages(user_Id); + break; + } + + case "send_message": { + response = SidebarService.handleSendMessage(requestJson); + break; + } + default: response = new ResponseModel("error", "Unknown action: " + action); } diff --git a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java index e81f24b..a63c05b 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java +++ b/src/main/java/org/to/telegramfinalproject/Server/SidebarService.java @@ -1,10 +1,16 @@ package org.to.telegramfinalproject.Server; +import org.json.JSONArray; import org.json.JSONObject; +import org.to.telegramfinalproject.Database.MessageDatabase; +import org.to.telegramfinalproject.Database.PrivateChatDatabase; import org.to.telegramfinalproject.Database.userDatabase; +import org.to.telegramfinalproject.Models.Message; import org.to.telegramfinalproject.Models.ResponseModel; import org.to.telegramfinalproject.Models.User; +import java.time.LocalDateTime; +import java.util.List; import java.util.Objects; import java.util.UUID; @@ -150,6 +156,81 @@ public class SidebarService { } } + // Get saved messages data + public static ResponseModel handleGetSavedMessages(UUID userId) { + try { + + UUID chatId = PrivateChatDatabase.getOrCreateSavedMessagesChat(userId); + if (chatId == null) { + return new ResponseModel("error", "Failed to create or find saved messages chat."); + } + + List messages = MessageDatabase.privateChatHistory(userId, userId); + + JSONArray messageArray = new JSONArray(); + if (!messages.isEmpty()) { + for (Message msg : messages) { + JSONObject msgJson = new JSONObject(); + msgJson.put("message_id", msg.getMessage_id().toString()); + msgJson.put("sender_id", msg.getSender_id().toString()); + msgJson.put("receiver_type", msg.getReceiver_type()); + msgJson.put("receiver_id", msg.getReceiver_id().toString()); + msgJson.put("content", msg.getContent()); + msgJson.put("message_type", msg.getMessage_type()); + msgJson.put("file_url", msg.getFile_url() != null ? msg.getFile_url() : JSONObject.NULL); + msgJson.put("send_at", msg.getSend_at().toString()); // LocalDateTime + msgJson.put("status", msg.getStatus()); + msgJson.put("reply_to_id", msg.getReply_to_id() != null ? msg.getReply_to_id().toString() : JSONObject.NULL); + msgJson.put("is_edited", msg.isIs_edited()); + msgJson.put("original_message_id", msg.getOriginal_message_id() != null ? msg.getOriginal_message_id().toString() : JSONObject.NULL); + msgJson.put("forwarded_by", msg.getForwarded_by() != null ? msg.getForwarded_by().toString() : JSONObject.NULL); + msgJson.put("forwarded_from", msg.getForwarded_from() != null ? msg.getForwarded_from().toString() : JSONObject.NULL); + + messageArray.put(msgJson); + } + } + + JSONObject data = new JSONObject(); + data.put("chat_id", chatId.toString()); + data.put("messages", messageArray); + + return new ResponseModel("success", "Saved messages retrieved successfully", data); + + } catch (Exception e) { + e.printStackTrace(); + return new ResponseModel("error", "Unexpected server error."); + } + } + + // Save messages to DB + public static ResponseModel handleSendMessage(JSONObject requestJson) { + try { + Message message = new Message( + UUID.fromString(requestJson.getString("message_id")), + UUID.fromString(requestJson.getString("sender_id")), + requestJson.getString("receiver_type"), + UUID.fromString(requestJson.getString("receiver_id")), + requestJson.optString("content", null), + requestJson.optString("message_type", "TEXT"), + requestJson.isNull("file_url") ? null : requestJson.getString("file_url"), + LocalDateTime.now(), // send_at + requestJson.optString("status", "SEND"), + requestJson.isNull("reply_to_id") ? null : UUID.fromString(requestJson.getString("reply_to_id")), + requestJson.optBoolean("is_edited", false), + requestJson.isNull("original_message_id") ? null : UUID.fromString(requestJson.getString("original_message_id")), + requestJson.isNull("forwarded_by") ? null : UUID.fromString(requestJson.getString("forwarded_by")), + requestJson.isNull("forwarded_from") ? null : UUID.fromString(requestJson.getString("forwarded_from")) + ); + + MessageDatabase.save(message); + return new ResponseModel("success", "Message saved successfully."); + + } catch (Exception e) { + e.printStackTrace(); + return new ResponseModel("error", "Unexpected server error."); + } + } + // Changes username public static boolean updateUserName(String userId, String newUserName) { return false; From fd399372a537230115cad796649d240cf8669e0d Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Sun, 3 Aug 2025 11:41:41 +0330 Subject: [PATCH 10/21] init.sql updated. --- src/main/resources/init.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/init.sql b/src/main/resources/init.sql index 5e5c7a4..e4b7d1c 100644 --- a/src/main/resources/init.sql +++ b/src/main/resources/init.sql @@ -71,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, From 4fe385b021efcc7eac386aded9dcad23af14177a Mon Sep 17 00:00:00 2001 From: Asal Lotfi Date: Sun, 3 Aug 2025 18:40:12 +0330 Subject: [PATCH 11/21] Sidebar Menu updated. --- .../Client/SidebarHandler.java | 1 - .../UI/TelegramApplication.java | 2 +- .../telegramfinalproject/CSS/sidebar_menu.css | 11 ++++ .../to/telegramfinalproject/sidebar_menu.fxml | 50 +++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/org/to/telegramfinalproject/CSS/sidebar_menu.css create mode 100644 src/main/resources/org/to/telegramfinalproject/sidebar_menu.fxml diff --git a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java index 258d9f5..035abe3 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/SidebarHandler.java @@ -5,7 +5,6 @@ import org.json.JSONException; import org.json.JSONObject; import org.to.telegramfinalproject.Models.Message; -import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java b/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java index 47a1970..1fdfd1f 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java +++ b/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java @@ -12,7 +12,7 @@ import java.io.IOException; public class TelegramApplication extends Application { @Override public void start(Stage stage) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/Intro.fxml")); + FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/sidebar_menu.fxml")); Scene scene = new Scene(fxmlLoader.load(), 1480, 820); stage.setTitle("Telegram"); stage.setScene(scene); diff --git a/src/main/resources/org/to/telegramfinalproject/CSS/sidebar_menu.css b/src/main/resources/org/to/telegramfinalproject/CSS/sidebar_menu.css new file mode 100644 index 0000000..b47abda --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/CSS/sidebar_menu.css @@ -0,0 +1,11 @@ +.menu-button { + -fx-background-color: transparent; + -fx-text-fill: white; + -fx-font-size: 13px; + -fx-alignment: CENTER_LEFT; + -fx-padding: 6 10 6 10; +} + +.menu-button:hover { + -fx-background-color: #2c3e50; +} \ No newline at end of file diff --git a/src/main/resources/org/to/telegramfinalproject/sidebar_menu.fxml b/src/main/resources/org/to/telegramfinalproject/sidebar_menu.fxml new file mode 100644 index 0000000..3286e6f --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/sidebar_menu.fxml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + +