Delete messages in chats

This commit is contained in:
2025-08-07 13:03:01 +03:30
parent f49dfacc33
commit 2c19db1d42
5 changed files with 438 additions and 3 deletions
@@ -1242,7 +1242,8 @@ public class ActionHandler {
System.out.println("4. Delete chat (both sides)");
System.out.println("5. View profile");
System.out.println("6. Archive/Unarchived");
System.out.println("7. Back");
System.out.println("7. View messages");
System.out.println("8. Back");
String input = scanner.nextLine();
@@ -1288,8 +1289,11 @@ public class ActionHandler {
return true;
}
case "7" ->{
viewMessagesInChat(chat);
}
case "7" -> {
case "8" -> {
return false;
}
default -> System.out.println("Invalid choice.");
@@ -1355,6 +1359,7 @@ public class ActionHandler {
}
System.out.println("12. Archive/Unarchived");
System.out.println("13. View messages");
System.out.println("0. Back to Chat List");
@@ -1409,6 +1414,10 @@ public class ActionHandler {
toggleArchive(chat.getId() , "group");
}
case "13" ->{
viewMessagesInChat(chat);
}
case "0" -> {
return false;
}
@@ -1476,6 +1485,7 @@ public class ActionHandler {
}
System.out.println("12. Archive/Unarchived");
System.out.println("13. View messages");
System.out.println("0. Back to Chat List");
String input = scanner.nextLine();
@@ -1558,7 +1568,9 @@ public class ActionHandler {
case "12"->{
toggleArchive(chat.getId() , "channel");
}
case "13" ->{
viewMessagesInChat(chat);
}
case "0" -> {
return false;
@@ -3327,8 +3339,173 @@ public class ActionHandler {
}
}
private void viewMessagesInChat(ChatEntry chat) {
int offset = 0;
int limit = 10;
while (true) {
JSONObject req = new JSONObject();
req.put("action", "get_chat_messages");
req.put("chat_id", chat.getId());
req.put("chat_type", chat.getType());
req.put("offset", offset);
req.put("limit", limit);
JSONObject res = sendWithResponse(req);
if (res == null || !res.getString("status").equals("success")) {
System.out.println("❌ Failed to fetch messages.");
return;
}
JSONArray messages = res.getJSONObject("data").getJSONArray("get_chat_messages");
if (messages.isEmpty()) {
if (offset == 0)
System.out.println("📭 No messages in this chat.");
else
System.out.println("📭 No more messages.");
return;
}
System.out.println("\n📥 Messages:");
System.out.println("─────────────────────────────────────────────");
for (int i = 0; i < messages.length(); i++) {
JSONObject msg = messages.getJSONObject(i);
String senderName = msg.optString("sender_name", "Unknown");
String content = msg.optString("content", "(no content)");
String time = msg.optString("time", "");
boolean isEdited = msg.optBoolean("is_edited", false);
String label = isEdited ? "🖊️ (edited)" : "";
System.out.printf("[%d] [%s] %s: %s %s\n", i + 1, time, senderName, content, label);
}
System.out.println("─────────────────────────────────────────────");
System.out.print("""
💬 Options:
[number] - Interact with message
N - Next page (older messages)
0 - Back to chat menu
➤ Choice: """);
String input = scanner.nextLine().trim();
if (input.equals("0")) return;
if (input.equalsIgnoreCase("N")) {
offset += limit;
continue;
}
try {
int index = Integer.parseInt(input);
if (index < 1 || index > messages.length()) {
System.out.println("❌ Invalid message number.");
continue;
}
JSONObject selected = messages.getJSONObject(index - 1);
UUID messageId = UUID.fromString(selected.getString("message_id"));
UUID senderId = UUID.fromString(selected.getString("sender_id"));
System.out.println("\n🎯 Selected message by " + selected.getString("sender_name"));
System.out.println("1. Reply");
System.out.println("2. Forward");
System.out.println("3. React");
if (senderId.toString().equals(Session.currentUser.getString("internal_uuid"))) {
System.out.println("4. Edit");
System.out.println("5. Delete");
}
System.out.println("0. Back to message list");
String choice = scanner.nextLine().trim();
switch (choice) {
case "1" -> replyToMessage(messageId);
case "2" -> forwardMessage(messageId);
case "3" -> reactToMessage(messageId);
case "4" -> {
if (senderId.toString().equals(Session.currentUser.getString("internal_uuid")))
editMessage(messageId);
}
case "5" -> {
if (senderId.toString().equals(Session.currentUser.getString("internal_uuid")))
deleteMessage(messageId);
}
case "0" -> {}
default -> System.out.println("❌ Invalid option.");
}
} catch (NumberFormatException e) {
System.out.println("❌ Please enter a valid number or command.");
}
}
}
private String editMessage(UUID messageId) {
return "not ready";
}
private String reactToMessage(UUID messageId) {
return "not ready";
}
private String forwardMessage(UUID messageId) {
return "not ready";
}
private String replyToMessage(UUID messageId) {
return "not ready";
}
private void deleteMessage(UUID messageId) {
System.out.println("\n🗑️ Delete Message Options:");
System.out.println("1. Delete for yourself (one-sided)");
System.out.println("2. Delete for everyone (global) [only if allowed]");
System.out.println("0. Cancel");
String choice = scanner.nextLine().trim();
switch (choice) {
case "1" -> {
JSONObject req = new JSONObject();
req.put("action", "delete_message");
req.put("message_id", messageId.toString());
req.put("delete_type", "one-sided");
JSONObject res = sendWithResponse(req);
if (res != null && res.getString("status").equals("success")) {
System.out.println("✅ Message deleted for you.");
} else {
System.out.println("❌ Failed to delete message.");
}
}
case "2" -> {
JSONObject req = new JSONObject();
req.put("action", "delete_message");
req.put("message_id", messageId.toString());
req.put("delete_type", "global");
JSONObject res = sendWithResponse(req);
if (res != null && res.getString("status").equals("success")) {
System.out.println("✅ Message deleted for everyone.");
} else {
System.out.println("❌ Failed to delete message globally.");
if (res != null) System.out.println("⚠️ " + res.optString("message"));
}
}
case "0" -> {
System.out.println("❎ Delete canceled.");
}
default -> {
System.out.println("❌ Invalid option.");
}
}
}
@@ -111,7 +111,116 @@ public class MessageDatabase {
}
}
public static List<Message> getMessagesForChat(UUID chatId, String chatType, UUID currentUserId, int offset, int limit) {
List<Message> messages = new ArrayList<>();
String sql = """
SELECT * FROM messages m
WHERE m.receiver_type = ?
AND m.receiver_id = ?
AND m.is_deleted_globally = FALSE
AND NOT EXISTS (
SELECT 1 FROM deleted_messages d
WHERE d.message_id = m.message_id
AND d.user_id = ?
)
ORDER BY m.send_at DESC
LIMIT ? OFFSET ?
""";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, chatType);
ps.setObject(2, chatId);
ps.setObject(3, currentUserId);
ps.setInt(4, limit);
ps.setInt(5, offset);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Message msg = 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.getTimestamp("send_at").toLocalDateTime(),
rs.getString("status"),
rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
rs.getBoolean("is_edited"),
rs.getBoolean("is_deleted_globally"),
rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null
);
messages.add(msg);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return messages;
}
public static Message findById(UUID messageId) {
String sql = "SELECT * FROM messages WHERE message_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, messageId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
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.getTimestamp("send_at").toLocalDateTime(),
rs.getString("status"),
rs.getObject("reply_to_id") != null ? UUID.fromString(rs.getString("reply_to_id")) : null,
rs.getBoolean("is_edited"),
rs.getBoolean("is_deleted_globally"),
rs.getObject("original_message_id") != null ? UUID.fromString(rs.getString("original_message_id")) : null,
rs.getObject("forwarded_by") != null ? UUID.fromString(rs.getString("forwarded_by")) : null,
rs.getObject("forwarded_from") != null ? UUID.fromString(rs.getString("forwarded_from")) : null
);
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static boolean markAsDeletedForUser(UUID messageId, UUID userId) {
String sql = "INSERT INTO deleted_messages (message_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, messageId);
ps.setObject(2, userId);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static boolean markAsGloballyDeleted(UUID messageId) {
String sql = "UPDATE messages SET is_deleted_globally = true WHERE message_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, messageId);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public void markMessageAsRead(UUID messageId, UUID userId) {
@@ -752,4 +861,6 @@ public class MessageDatabase {
}
@@ -20,6 +20,9 @@ public class Message {
private UUID forwarded_by;
private UUID forwarded_from;
private List<FileAttachment> attachments;
private transient String sender_name;
private transient String receiver_name;
// Full Constructor
public Message(UUID message_id, UUID sender_id, String receiver_type, UUID receiver_id, String content,
@@ -101,4 +104,20 @@ public class Message {
public List<FileAttachment> getAttachments() { return attachments; }
public void setAttachments(List<FileAttachment> attachments) { this.attachments = attachments; }
public String getSender_name() {
return sender_name;
}
public void setSender_name(String sender_name) {
this.sender_name = sender_name;
}
public String getReceiver_name() {
return receiver_name;
}
public void setReceiver_name(String receiver_name) {
this.receiver_name = receiver_name;
}
}
@@ -2104,7 +2104,111 @@ public class ClientHandler implements Runnable {
break;
}
case "get_chat_messages" : {
UUID chatId = UUID.fromString(requestJson.getString("chat_id"));
String chatType = requestJson.getString("chat_type");
int offset = requestJson.optInt("offset", 0); // پیش‌فرض 0
int limit = requestJson.optInt("limit", 10); // پیش‌فرض 10
List<Message> messages = MessageDatabase.getMessagesForChat(chatId, chatType, currentUser.getInternal_uuid(), offset, limit);
JSONArray result = new JSONArray();
for (Message m : messages) {
JSONObject obj = new JSONObject();
obj.put("message_id", m.getMessage_id().toString());
obj.put("sender_id", m.getSender_id().toString());
User sender = userDatabase.findByInternalUUID(m.getSender_id());
obj.put("sender_name", sender != null ? sender.getProfile_name() : "Unknown");
obj.put("receiver_id", m.getReceiver_id().toString());
obj.put("receiver_type", m.getReceiver_type());
String receiverName = switch (m.getReceiver_type()) {
case "group" -> {
Group g = GroupDatabase.findByInternalUUID(m.getReceiver_id());
yield g != null ? g.getGroup_name() : "Unknown group";
}
case "channel" -> {
Channel c = ChannelDatabase.findByInternalUUID(m.getReceiver_id());
yield c != null ? c.getChannel_name() : "Unknown channel";
}
case "private" -> {
UUID otherId = m.getSender_id().equals(currentUser.getInternal_uuid()) ? m.getReceiver_id() : m.getSender_id();
User other = userDatabase.findByInternalUUID(otherId);
yield other != null ? other.getProfile_name() : "Unknown user";
}
default -> "Unknown";
};
obj.put("receiver_name", receiverName);
obj.put("content", m.getContent());
obj.put("message_type", m.getMessage_type());
obj.put("time", m.getSend_at().toString());
obj.put("is_edited", m.isIs_edited());
obj.put("is_deleted_globally", m.isIs_deleted_globally());
result.put(obj);
}
JSONObject data = new JSONObject();
data.put("get_chat_messages", result);
response = new ResponseModel("success", "get messages ", data);
break;
}
case "delete_message" : {
UUID messageId = UUID.fromString(requestJson.getString("message_id"));
String deleteType = requestJson.getString("delete_type");
Message msg = MessageDatabase.findById(messageId);
if (msg == null) {
response = new ResponseModel("error", "Message not found.");
break;
}
UUID currentUserId = currentUser.getInternal_uuid();
if (deleteType.equals("one-sided")) {
boolean success = MessageDatabase.markAsDeletedForUser(messageId, currentUserId);
if (success)
response = new ResponseModel("success", "Message deleted for current user.");
else
response = new ResponseModel("error", "Failed to delete message for user.");
break;
}
//If allowed to delete the message
if (deleteType.equals("global")) {
boolean allowed = false;
String type = msg.getReceiver_type();
UUID chatId = msg.getReceiver_id();
if (type.equals("private") || type.equals("group")) {
allowed = msg.getSender_id().equals(currentUserId);
} else if (type.equals("channel")) {
//Only owner and admins can delete messages
allowed = ChannelPermissionUtil.canDeleteMessage(chatId, currentUserId);
}
if (!allowed) {
response = new ResponseModel("error", "You are not allowed to delete this message globally.");
break;
}
boolean success = MessageDatabase.markAsGloballyDeleted(messageId);
if (success)
response = new ResponseModel("success", "Message deleted globally.");
else
response = new ResponseModel("error", "Failed to delete message globally.");
break;
}
response = new ResponseModel("error", "Invalid delete_type.");
break;
}
@@ -2289,4 +2393,6 @@ public class ClientHandler implements Runnable {
}
@@ -2,7 +2,12 @@ package org.to.telegramfinalproject.Utils;
import org.json.JSONObject;
import org.to.telegramfinalproject.Database.ChannelDatabase;
import org.to.telegramfinalproject.Database.ConnectionDb;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
public class ChannelPermissionUtil {
@@ -51,4 +56,21 @@ public class ChannelPermissionUtil {
}
return false;
}
public static boolean canDeleteMessage(UUID channelId, UUID userId) {
String roleSql = "SELECT role FROM channel_subscribers WHERE channel_id = ? AND user_id = ?";
try (Connection conn = ConnectionDb.connect();
PreparedStatement ps = conn.prepareStatement(roleSql)) {
ps.setObject(1, channelId);
ps.setObject(2, userId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
String role = rs.getString("role");
return role.equalsIgnoreCase("owner") || role.equalsIgnoreCase("admin");
}
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
}