diff --git a/backend/app/chats/router.py b/backend/app/chats/router.py index 6395f99..76d624c 100644 --- a/backend/app/chats/router.py +++ b/backend/app/chats/router.py @@ -35,6 +35,10 @@ async def send_message(message: MessageCreate, user: UserModel = Depends(get_cur async def edit_message(message_update: MessageUpdate, user: UserModel = Depends(get_current_verified_user)) -> Message: return await ChatService.update_message(user, message_update) +@router.delete("/message") +async def delete_message(message_id: uuid.UUID, user: UserModel = Depends(get_current_verified_user)): + await ChatService.delete_message(user, message_id) + return {"status": True, "message": "Message successfully deleted"} @router.websocket("/ws") async def websocket_endpoint(ws: WebSocket, user: UserModel = Depends(get_current_verified_user)): diff --git a/backend/app/chats/service.py b/backend/app/chats/service.py index 9ed0532..8ee82ba 100644 --- a/backend/app/chats/service.py +++ b/backend/app/chats/service.py @@ -147,26 +147,41 @@ class ChatService: for user_id in user_ids: payload = { "user_id": str(user_id), + "type": "send", "data": message.model_dump(mode='json') } await redis_client.publish("messenger_updates", json.dumps(payload)) log.debug(f"Published message for user_id: {user_id}") + @classmethod + async def _delete_ws_message(cls, user_ids: List[int], message_id: uuid.UUID): + redis_client = await get_redis() + for user_id in user_ids: + payload = { + "user_id": str(user_id), + "type": "del", + "data": str(message_id) + } + await redis_client.publish("messenger_updates", json.dumps(payload)) + log.debug(f"Published message for user_id: {user_id}") + + @classmethod async def update_message(cls, user: UserModel, message_update: MessageUpdate) -> Message: async with async_session_maker() as session: message_exist = await MessageDAO.find_one_or_none( session, - and_( - MessageModel.id==message_update.id, - MessageModel.sender_id==user.id - ) + MessageModel.id==message_update.id ) if message_exist is None: log.warning("Message not found", extra={"user_id": user.id, "message_id": message_update.id}) raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Message not found") + if message_exist.sender_id != user.id: + log.warning("User does not have access to this message", + extra={"user_id": user.id, "message_id": message_update.id}) + raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Message not allowed") message_update_db = await MessageDAO.update( @@ -192,3 +207,35 @@ class ChatService: await session.commit() log.info("Message update successfully", extra={"user_id": user.id, "message_id": message_update.id}) return message_update_db + + + @classmethod + async def delete_message(cls, user: UserModel, message_id: uuid.UUID): + async with async_session_maker() as session: + message_exist = await MessageDAO.find_one_or_none( + session, + MessageModel.id==message_id, + ) + + if message_exist is None: + log.warning("Message not found", extra={"user_id": user.id, "message_id": message_id}) + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Message not found") + if message_exist.sender_id != user.id: + log.warning("User does not have access to this message", + extra={"user_id": user.id, "message_id": message_id}) + raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Message not allowed") + + members = await ParticipantDAO.find_all( + session, + None, + None, + ParticipantModel.chat_id==message_exist.chat_id + ) + member_ids = [member.user_id for member in members] + + await MessageDAO.delete(session, MessageModel.id==message_id) + + await cls._delete_ws_message(member_ids, message_id) + + await session.commit() + log.info("Message delete successfully", extra={"user_id": user.id, "message_id": message_exist.id}) \ No newline at end of file diff --git a/backend/app/core/config.py b/backend/app/core/config.py index a15ab4d..f64325b 100755 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -66,7 +66,7 @@ class Settings(BaseSettings): def RABBITMQ_URL(self) -> str: return f"amqp://{self.RMQ_USER}:{self.RMQ_PASS}@{self.RMQ_HOST}:{self.RMQ_PORT}//" - model_config = SettingsConfigDict(env_file="../.env.prod", extra="allow") + model_config = SettingsConfigDict(env_file="../.env", extra="allow") settings: Settings = Settings() \ No newline at end of file diff --git a/backend/app/services/messenger_service.py b/backend/app/services/messenger_service.py index b595266..cd95e4a 100644 --- a/backend/app/services/messenger_service.py +++ b/backend/app/services/messenger_service.py @@ -40,12 +40,16 @@ class PubSubMessenger: handler = cls.get_handlers().get(message['channel']) if handler: - await handler(payload["data"], ws) + await handler(payload, ws) @classmethod - async def handle_message(cls, message, ws: WebSocket): - await ws.send_json(message) + async def handle_message(cls, payload, ws: WebSocket): + log.debug("Message start sending type: %s", payload["type"]) + if payload["type"] == "send": + await ws.send_json(payload["data"]) + elif payload["type"] == "del": + await ws.send_json({"type": "del", "message_id": payload["data"]}) log.info(f"Message sent to user via WebSocket") diff --git a/frontend/src/pages/ChatPage.tsx b/frontend/src/pages/ChatPage.tsx index 07e888b..89bbcb0 100644 --- a/frontend/src/pages/ChatPage.tsx +++ b/frontend/src/pages/ChatPage.tsx @@ -2,7 +2,7 @@ import { useAuthStore } from '../store/authStore'; import { useChatStore } from '../store/chatStore'; import { useNavigate, useParams } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; -import { MessageSquarePlus, Settings, User, Send, ArrowLeft, X, Pencil, Check, ArrowDown } from 'lucide-react'; +import { MessageSquarePlus, Settings, User, Send, ArrowLeft, X, Pencil, Check, ArrowDown, Trash2 } from 'lucide-react'; import miniLogo from '../assets/mini-logo.png'; import VerificationBanner from '../components/common/VerificationBanner'; import { useEffect, useState, useRef } from 'react'; @@ -120,8 +120,17 @@ export default function ChatPage() { ws.onmessage = (event) => { try { - const message: Message = JSON.parse(event.data); - console.log('Received message via WebSocket:', message); + const data = JSON.parse(event.data); + console.log('Received WebSocket data:', data); + + // Handle message deletion + if (data.type === 'del' && data.message_id) { + setMessages(prev => prev.filter(m => m.id !== data.message_id)); + return; + } + + // Handle regular message (add/update) + const message: Message = data; // Add or update message in current chat if it belongs to it if (selectedChat && message.chat_id === selectedChat.chat_id) { @@ -429,6 +438,18 @@ export default function ChatPage() { handleCancelEdit(); } }; + + const handleDeleteMessage = async (messageId: string) => { + if (!confirm('Удалить сообщение?')) return; + + try { + await chatService.deleteMessage(messageId); + // Message will be removed via WebSocket + } catch (err: any) { + console.error('Failed to delete message:', err); + alert('Не удалось удалить сообщение'); + } + }; return (