Merge pull request #3 from PartowRoshani/Login/Register
Searching system in messages and update status and last seen for users
This commit is contained in:
@@ -11,6 +11,7 @@ import java.util.Scanner;
|
|||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.to.telegramfinalproject.Models.ChatEntry;
|
import org.to.telegramfinalproject.Models.ChatEntry;
|
||||||
|
import org.to.telegramfinalproject.Models.SearchRequestModel;
|
||||||
|
|
||||||
public class ActionHandler {
|
public class ActionHandler {
|
||||||
private final PrintWriter out;
|
private final PrintWriter out;
|
||||||
@@ -63,12 +64,15 @@ public class ActionHandler {
|
|||||||
System.out.print("Enter keyword to search: ");
|
System.out.print("Enter keyword to search: ");
|
||||||
String keyword = scanner.nextLine();
|
String keyword = scanner.nextLine();
|
||||||
|
|
||||||
JSONObject request = new JSONObject();
|
if (Session.currentUser == null || !Session.currentUser.has("user_id")) {
|
||||||
request.put("action", "search");
|
System.out.println("You must be logged in to search.");
|
||||||
request.put("keyword", keyword);
|
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) {
|
private void send(JSONObject request) {
|
||||||
@@ -121,7 +125,14 @@ public class ActionHandler {
|
|||||||
System.out.println("\nSearch Results:");
|
System.out.println("\nSearch Results:");
|
||||||
for (Object obj : results) {
|
for (Object obj : results) {
|
||||||
JSONObject item = (JSONObject) obj;
|
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;
|
break;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class MessageDatabase {
|
public class MessageDatabase {
|
||||||
|
|
||||||
@@ -132,4 +133,122 @@ public class MessageDatabase {
|
|||||||
return null;
|
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<Message> searchMessagesForUser(UUID userId, String keyword) {
|
||||||
|
List<Message> 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<Message> searchMessagesInGroups(List<UUID> groupIds, String keyword) {
|
||||||
|
List<Message> 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<Message> searchMessagesInChannels(List<UUID> channelIds, String keyword) {
|
||||||
|
List<Message> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,12 +208,14 @@ public class userDatabase {
|
|||||||
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
PreparedStatement stmt = conn.prepareStatement(sql)) {
|
||||||
stmt.setString(1, status);
|
stmt.setString(1, status);
|
||||||
stmt.setObject(2, uuid);
|
stmt.setObject(2, uuid);
|
||||||
stmt.executeUpdate();
|
int rows = stmt.executeUpdate();
|
||||||
|
System.out.println("🔁 updateUserStatus: set '" + status + "' for " + uuid + " → affected rows = " + rows);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void updateLastSeen(UUID uuid) {
|
public static void updateLastSeen(UUID uuid) {
|
||||||
String sql = "UPDATE users SET last_seen = CURRENT_TIMESTAMP WHERE internal_uuid = ?";
|
String sql = "UPDATE users SET last_seen = CURRENT_TIMESTAMP WHERE internal_uuid = ?";
|
||||||
try (Connection conn = ConnectionDb.connect();
|
try (Connection conn = ConnectionDb.connect();
|
||||||
@@ -263,14 +265,21 @@ public class userDatabase {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
public List<User> searchUsers(String keyword, UUID currentUserId) {
|
||||||
|
String query = """
|
||||||
|
SELECT * FROM users
|
||||||
|
WHERE (user_id ILIKE ? OR profile_name ILIKE ?)
|
||||||
|
AND internal_uuid <> ?
|
||||||
|
""";
|
||||||
|
|
||||||
public List<User> searchUsers(String keyword) {
|
|
||||||
String query = "SELECT * FROM users WHERE user_id ILIKE ? OR profile_name ILIKE ?"; //(ILIKE) case_insensitive
|
|
||||||
List<User> result = new ArrayList<>();
|
List<User> result = new ArrayList<>();
|
||||||
try (Connection conn = getConnection();
|
try (Connection conn = getConnection();
|
||||||
PreparedStatement stmt = conn.prepareStatement(query)) {
|
PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||||
|
|
||||||
stmt.setString(1, "%" + keyword + "%");
|
stmt.setString(1, "%" + keyword + "%");
|
||||||
stmt.setString(2, "%" + keyword + "%");
|
stmt.setString(2, "%" + keyword + "%");
|
||||||
|
stmt.setObject(3, currentUserId);
|
||||||
|
|
||||||
ResultSet rs = stmt.executeQuery();
|
ResultSet rs = stmt.executeQuery();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
result.add(extractUser(rs));
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
package org.to.telegramfinalproject.Models;
|
package org.to.telegramfinalproject.Models;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class SearchRequestModel {
|
public class SearchRequestModel {
|
||||||
private String action;
|
private String action;
|
||||||
private String keyword;
|
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.action = action;
|
||||||
this.keyword = keyword;
|
this.keyword = keyword;
|
||||||
|
this.user_id = user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAction() {
|
public JSONObject toJson() {
|
||||||
return action;
|
JSONObject json = new JSONObject();
|
||||||
}
|
json.put("action", action);
|
||||||
|
json.put("keyword", keyword);
|
||||||
public String getKeyword() {
|
json.put("user_id", user_id);
|
||||||
return keyword;
|
return json;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import java.util.*;
|
|||||||
public class ClientHandler implements Runnable {
|
public class ClientHandler implements Runnable {
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
private final AuthService authService = new AuthService();
|
private final AuthService authService = new AuthService();
|
||||||
|
private User currentUser;
|
||||||
|
|
||||||
|
|
||||||
public ClientHandler(Socket socket) {
|
public ClientHandler(Socket socket) {
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
@@ -20,6 +22,8 @@ public class ClientHandler implements Runnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
UUID userId = null;
|
||||||
|
|
||||||
try (
|
try (
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)
|
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)
|
||||||
@@ -63,15 +67,16 @@ public class ClientHandler implements Runnable {
|
|||||||
? new ResponseModel("success", "Registration successful.")
|
? new ResponseModel("success", "Registration successful.")
|
||||||
: new ResponseModel("error", "Registration failed.");
|
: new ResponseModel("error", "Registration failed.");
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
User user = authService.login(request.getUsername(), request.getPassword());
|
User user = authService.login(request.getUsername(), request.getPassword());
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
response = new ResponseModel("error", "Login failed.");
|
response = new ResponseModel("error", "Login failed.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
this.currentUser = user;
|
||||||
|
|
||||||
SessionManager.addUser(user.getInternal_uuid(), this.socket);
|
SessionManager.addUser(user.getInternal_uuid(), this.socket);
|
||||||
userDatabase.updateUserStatus(user.getInternal_uuid(), "online");
|
userDatabase.updateUserStatus(user.getInternal_uuid(), "online");
|
||||||
|
|
||||||
List<Contact> contacts = ContactDatabase.getContacts(user.getInternal_uuid());
|
List<Contact> contacts = ContactDatabase.getContacts(user.getInternal_uuid());
|
||||||
List<Group> groups = GroupDatabase.getGroupsByUser(user.getInternal_uuid());
|
List<Group> groups = GroupDatabase.getGroupsByUser(user.getInternal_uuid());
|
||||||
List<Channel> channels = ChannelDatabase.getChannelsByUser(user.getInternal_uuid());
|
List<Channel> channels = ChannelDatabase.getChannelsByUser(user.getInternal_uuid());
|
||||||
@@ -112,9 +117,9 @@ public class ClientHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "logout": {
|
case "logout": {
|
||||||
String userId = requestJson.optString("user_id");
|
String user_Id = requestJson.optString("user_id");
|
||||||
if (userId != null && !userId.isEmpty()) {
|
if (userId != null && !user_Id.isEmpty()) {
|
||||||
UUID uuid = UUID.fromString(userId);
|
UUID uuid = UUID.fromString(user_Id);
|
||||||
userDatabase.updateUserStatus(uuid, "offline");
|
userDatabase.updateUserStatus(uuid, "offline");
|
||||||
userDatabase.updateLastSeen(uuid);
|
userDatabase.updateLastSeen(uuid);
|
||||||
SessionManager.removeUser(uuid);
|
SessionManager.removeUser(uuid);
|
||||||
@@ -128,8 +133,11 @@ public class ClientHandler implements Runnable {
|
|||||||
case "search": {
|
case "search": {
|
||||||
String keyword = requestJson.optString("keyword");
|
String keyword = requestJson.optString("keyword");
|
||||||
List<JSONObject> results = new ArrayList<>();
|
List<JSONObject> 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();
|
JSONObject obj = new JSONObject();
|
||||||
obj.put("type", "user");
|
obj.put("type", "user");
|
||||||
obj.put("id", u.getUser_id());
|
obj.put("id", u.getUser_id());
|
||||||
@@ -153,6 +161,50 @@ public class ClientHandler implements Runnable {
|
|||||||
results.add(obj);
|
results.add(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Message> 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<UUID> groupIds = new ArrayList<>();
|
||||||
|
for (Group g : GroupDatabase.getGroupsByUser(currentUser.getInternal_uuid())) {
|
||||||
|
groupIds.add(g.getInternal_uuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UUID> channelIds = new ArrayList<>();
|
||||||
|
for (Channel c : ChannelDatabase.getChannelsByUser(currentUser.getInternal_uuid())) {
|
||||||
|
channelIds.add(c.getInternal_uuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Message> 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<Message> 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();
|
JSONObject data = new JSONObject();
|
||||||
data.put("results", new JSONArray(results));
|
data.put("results", new JSONArray(results));
|
||||||
response = new ResponseModel("success", "Search results found", data);
|
response = new ResponseModel("success", "Search results found", data);
|
||||||
@@ -171,12 +223,29 @@ public class ClientHandler implements Runnable {
|
|||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.println("Connection with client lost.");
|
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) {
|
if (userId != null) {
|
||||||
userDatabase.updateUserStatus(userId, "offline");
|
userDatabase.updateUserStatus(userId, "offline");
|
||||||
userDatabase.updateLastSeen(userId);
|
userDatabase.updateLastSeen(userId);
|
||||||
SessionManager.removeUser(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.to.telegramfinalproject.Server;
|
package org.to.telegramfinalproject.Server;
|
||||||
|
|
||||||
|
import org.to.telegramfinalproject.Database.userDatabase;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
@@ -10,6 +12,8 @@ public class MainServer {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
|
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
|
||||||
System.out.println("Server started on port " + PORT);
|
System.out.println("Server started on port " + PORT);
|
||||||
|
userDatabase.setAllUsersOffline();
|
||||||
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Socket clientSocket = serverSocket.accept();
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
|||||||
Reference in New Issue
Block a user