diff --git a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java index 91b5db1..dcfd169 100644 --- a/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Client/ActionHandler.java @@ -11,6 +11,7 @@ import java.util.Scanner; import org.json.JSONArray; import org.json.JSONObject; import org.to.telegramfinalproject.Models.ChatEntry; +import org.to.telegramfinalproject.Models.SearchRequestModel; public class ActionHandler { private final PrintWriter out; @@ -63,12 +64,15 @@ public class ActionHandler { System.out.print("Enter keyword to search: "); String keyword = scanner.nextLine(); - JSONObject request = new JSONObject(); - request.put("action", "search"); - request.put("keyword", keyword); + if (Session.currentUser == null || !Session.currentUser.has("user_id")) { + System.out.println("You must be logged in to search."); + return; + } - send(request); + String userId = Session.currentUser.getString("user_id"); + SearchRequestModel model = new SearchRequestModel("search", keyword, userId); + send(model.toJson()); } private void send(JSONObject request) { @@ -121,7 +125,14 @@ public class ActionHandler { System.out.println("\nSearch Results:"); for (Object obj : results) { JSONObject item = (JSONObject) obj; - System.out.println("- [" + item.getString("type") + "] " + item.getString("name") + " (ID: " + item.getString("id") + ")"); + if (item.getString("type").equals("message")) { + System.out.println("- [message] \"" + item.getString("content") + "\"" + + " (from: " + item.getString("sender") + ", at: " + item.getString("time") + ")"); + } else { + System.out.println("- [" + item.getString("type") + "] " + + item.getString("name") + " (ID: " + item.getString("id") + ")"); + } + } } break; diff --git a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java index 9b5a814..3f959eb 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/MessageDatabase.java @@ -7,6 +7,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; public class MessageDatabase { @@ -132,4 +133,122 @@ public class MessageDatabase { return null; } + + public static Message extractMessage(ResultSet rs) throws SQLException { + return new Message( + UUID.fromString(rs.getString("message_id")), + UUID.fromString(rs.getString("sender_id")), + rs.getString("receiver_type"), + 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"), + rs.getBoolean("is_edited"), + (UUID) rs.getObject("original_message_id"), + (UUID) rs.getObject("forwarded_by"), + (UUID) rs.getObject("forwarded_from") + ); + } + + + + public static List searchMessagesForUser(UUID userId, String keyword) { + List result = new ArrayList<>(); + String sql = """ + SELECT * FROM messages + WHERE receiver_type = 'private' + AND (sender_id = ? OR receiver_id = ?) + AND content ILIKE ? + ORDER BY send_at DESC + """; + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, userId); + stmt.setObject(2, userId); + stmt.setString(3, "%" + keyword + "%"); + + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + result.add(extractMessage(rs)); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return result; + } + + + public static List searchMessagesInGroups(List groupIds, String keyword) { + List result = new ArrayList<>(); + if (groupIds.isEmpty()) return result; + + String placeholders = groupIds.stream().map(id -> "?").collect(Collectors.joining(", ")); + String sql = """ + SELECT * FROM messages + WHERE receiver_type = 'group' + AND receiver_id IN (%s) + AND content ILIKE ? + ORDER BY send_at DESC + """.formatted(placeholders); + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + int i = 1; + for (UUID id : groupIds) { + stmt.setObject(i++, id); + } + stmt.setString(i, "%" + keyword + "%"); + + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + result.add(extractMessage(rs)); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return result; + } + + + public static List searchMessagesInChannels(List channelIds, String keyword) { + List result = new ArrayList<>(); + if (channelIds.isEmpty()) return result; + + String placeholders = channelIds.stream().map(id -> "?").collect(Collectors.joining(", ")); + String sql = """ + SELECT * FROM messages + WHERE receiver_type = 'channel' + AND receiver_id IN (%s) + AND content ILIKE ? + ORDER BY send_at DESC + """.formatted(placeholders); + + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + int i = 1; + for (UUID id : channelIds) { + stmt.setObject(i++, id); + } + stmt.setString(i, "%" + keyword + "%"); + + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + result.add(extractMessage(rs)); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return result; + } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java index 8488fda..3427fa8 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java +++ b/src/main/java/org/to/telegramfinalproject/Database/userDatabase.java @@ -208,12 +208,14 @@ public class userDatabase { PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, status); stmt.setObject(2, uuid); - stmt.executeUpdate(); + int rows = stmt.executeUpdate(); + System.out.println("🔁 updateUserStatus: set '" + status + "' for " + uuid + " → affected rows = " + rows); } catch (SQLException e) { e.printStackTrace(); } } + public static void updateLastSeen(UUID uuid) { String sql = "UPDATE users SET last_seen = CURRENT_TIMESTAMP WHERE internal_uuid = ?"; try (Connection conn = ConnectionDb.connect(); @@ -263,14 +265,21 @@ public class userDatabase { return null; } + public List searchUsers(String keyword, UUID currentUserId) { + String query = """ + SELECT * FROM users + WHERE (user_id ILIKE ? OR profile_name ILIKE ?) + AND internal_uuid <> ? + """; - public List searchUsers(String keyword) { - String query = "SELECT * FROM users WHERE user_id ILIKE ? OR profile_name ILIKE ?"; //(ILIKE) case_insensitive List result = new ArrayList<>(); try (Connection conn = getConnection(); PreparedStatement stmt = conn.prepareStatement(query)) { + stmt.setString(1, "%" + keyword + "%"); stmt.setString(2, "%" + keyword + "%"); + stmt.setObject(3, currentUserId); + ResultSet rs = stmt.executeQuery(); while (rs.next()) { result.add(extractUser(rs)); @@ -282,5 +291,18 @@ public class userDatabase { } + + public static void setAllUsersOffline() { + String sql = "UPDATE users SET status = 'offline'"; + try (Connection conn = ConnectionDb.connect(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + int affected = stmt.executeUpdate(); + System.out.println("🔁 All users set to offline. Rows affected: " + affected); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + } diff --git a/src/main/java/org/to/telegramfinalproject/Models/SearchRequestModel.java b/src/main/java/org/to/telegramfinalproject/Models/SearchRequestModel.java index db63e19..4751d5c 100644 --- a/src/main/java/org/to/telegramfinalproject/Models/SearchRequestModel.java +++ b/src/main/java/org/to/telegramfinalproject/Models/SearchRequestModel.java @@ -1,19 +1,24 @@ package org.to.telegramfinalproject.Models; +import org.json.JSONObject; + public class SearchRequestModel { private String action; private String keyword; + private String user_id; - public SearchRequestModel(String action, String keyword) { + public SearchRequestModel(String action, String keyword, String user_id) { this.action = action; this.keyword = keyword; + this.user_id = user_id; } - public String getAction() { - return action; - } - - public String getKeyword() { - return keyword; + public JSONObject toJson() { + JSONObject json = new JSONObject(); + json.put("action", action); + json.put("keyword", keyword); + json.put("user_id", user_id); + return json; } } + diff --git a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java index 1058e3e..9a31c63 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java +++ b/src/main/java/org/to/telegramfinalproject/Server/ClientHandler.java @@ -13,6 +13,8 @@ import java.util.*; public class ClientHandler implements Runnable { private final Socket socket; private final AuthService authService = new AuthService(); + private User currentUser; + public ClientHandler(Socket socket) { this.socket = socket; @@ -20,6 +22,8 @@ public class ClientHandler implements Runnable { @Override public void run() { + UUID userId = null; + try ( BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream(), true) @@ -63,15 +67,16 @@ public class ClientHandler implements Runnable { ? new ResponseModel("success", "Registration successful.") : new ResponseModel("error", "Registration failed."); } else { + User user = authService.login(request.getUsername(), request.getPassword()); if (user == null) { response = new ResponseModel("error", "Login failed."); break; } + this.currentUser = user; SessionManager.addUser(user.getInternal_uuid(), this.socket); userDatabase.updateUserStatus(user.getInternal_uuid(), "online"); - List contacts = ContactDatabase.getContacts(user.getInternal_uuid()); List groups = GroupDatabase.getGroupsByUser(user.getInternal_uuid()); List channels = ChannelDatabase.getChannelsByUser(user.getInternal_uuid()); @@ -112,9 +117,9 @@ public class ClientHandler implements Runnable { } case "logout": { - String userId = requestJson.optString("user_id"); - if (userId != null && !userId.isEmpty()) { - UUID uuid = UUID.fromString(userId); + String user_Id = requestJson.optString("user_id"); + if (userId != null && !user_Id.isEmpty()) { + UUID uuid = UUID.fromString(user_Id); userDatabase.updateUserStatus(uuid, "offline"); userDatabase.updateLastSeen(uuid); SessionManager.removeUser(uuid); @@ -128,8 +133,11 @@ public class ClientHandler implements Runnable { case "search": { String keyword = requestJson.optString("keyword"); List results = new ArrayList<>(); + String user_Id = requestJson.getString("user_id"); + User currentUser = new userDatabase().findByUserId(user_Id); + UUID currentUserUUID = currentUser.getInternal_uuid(); - for (User u : new userDatabase().searchUsers(keyword)) { + for (User u : new userDatabase().searchUsers(keyword, currentUserUUID)) { JSONObject obj = new JSONObject(); obj.put("type", "user"); obj.put("id", u.getUser_id()); @@ -153,6 +161,50 @@ public class ClientHandler implements Runnable { results.add(obj); } + List matchedMessages = MessageDatabase.searchMessagesForUser(currentUser.getInternal_uuid(), keyword); + for (Message m : matchedMessages) { + JSONObject obj = new JSONObject(); + obj.put("type", "message"); + obj.put("content", m.getContent()); + obj.put("sender", m.getSender_id().toString()); + obj.put("time", m.getSend_at().toString()); + results.add(obj); + } + + + + List groupIds = new ArrayList<>(); + for (Group g : GroupDatabase.getGroupsByUser(currentUser.getInternal_uuid())) { + groupIds.add(g.getInternal_uuid()); + } + + List channelIds = new ArrayList<>(); + for (Channel c : ChannelDatabase.getChannelsByUser(currentUser.getInternal_uuid())) { + channelIds.add(c.getInternal_uuid()); + } + + + List groupMessages = MessageDatabase.searchMessagesInGroups(groupIds, keyword); + for (Message m : groupMessages) { + JSONObject obj = new JSONObject(); + obj.put("type", "message"); + obj.put("from", "group"); + obj.put("content", m.getContent()); + obj.put("time", m.getSend_at().toString()); + results.add(obj); + } + + List channelMessages = MessageDatabase.searchMessagesInChannels(channelIds, keyword); + for (Message m : channelMessages) { + JSONObject obj = new JSONObject(); + obj.put("type", "message"); + obj.put("from", "channel"); + obj.put("content", m.getContent()); + obj.put("time", m.getSend_at().toString()); + results.add(obj); + } + + JSONObject data = new JSONObject(); data.put("results", new JSONArray(results)); response = new ResponseModel("success", "Search results found", data); @@ -171,12 +223,29 @@ public class ClientHandler implements Runnable { } } catch (IOException e) { System.out.println("Connection with client lost."); - UUID userId = SessionManager.getUserIdBySocket(this.socket); + userId = (currentUser != null) ? currentUser.getInternal_uuid() : SessionManager.getUserIdBySocket(this.socket); if (userId != null) { userDatabase.updateUserStatus(userId, "offline"); userDatabase.updateLastSeen(userId); SessionManager.removeUser(userId); } + } finally { + try { + if (currentUser != null) { + userId = currentUser.getInternal_uuid(); + System.out.println("🔚 Client disconnected. Cleaning up user " + userId); + userDatabase.updateUserStatus(userId, "offline"); + userDatabase.updateLastSeen(userId); + SessionManager.removeUser(userId); + } else { + System.out.println("❗ currentUser is null, couldn't set offline."); + } + socket.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } } + + } } diff --git a/src/main/java/org/to/telegramfinalproject/Server/MainServer.java b/src/main/java/org/to/telegramfinalproject/Server/MainServer.java index 57f066a..5de2fbd 100644 --- a/src/main/java/org/to/telegramfinalproject/Server/MainServer.java +++ b/src/main/java/org/to/telegramfinalproject/Server/MainServer.java @@ -1,5 +1,7 @@ package org.to.telegramfinalproject.Server; +import org.to.telegramfinalproject.Database.userDatabase; + import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; @@ -10,6 +12,8 @@ public class MainServer { public static void main(String[] args) { try (ServerSocket serverSocket = new ServerSocket(PORT)) { System.out.println("Server started on port " + PORT); + userDatabase.setAllUsersOffline(); + while (true) { Socket clientSocket = serverSocket.accept();