From f7605812c1b5d33abead4861e372d9939d8ba789 Mon Sep 17 00:00:00 2001 From: Brunobrno Date: Fri, 26 Dec 2025 17:39:11 +0100 Subject: [PATCH] Implement chat backend and frontend scaffolding Expanded chat backend with message reply, edit, delete, and reaction support in consumers and models. Updated routing to use chat_id. Added chat-related view stubs. On the frontend, introduced ChatLayout and ChatPage scaffolding, and routed protected routes through ChatLayout. --- backend/social/chat/consumers.py | 121 +++++++++++++++++++++++++-- backend/social/chat/models.py | 9 ++ backend/social/chat/routing.py | 2 +- backend/social/chat/views.py | 22 +++++ frontend/src/App.tsx | 13 ++- frontend/src/layouts/ChatLayout.tsx | 21 +++++ frontend/src/pages/Chat/ChatPage.tsx | 0 7 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 frontend/src/layouts/ChatLayout.tsx create mode 100644 frontend/src/pages/Chat/ChatPage.tsx diff --git a/backend/social/chat/consumers.py b/backend/social/chat/consumers.py index 1b61ebb..39b5f0d 100644 --- a/backend/social/chat/consumers.py +++ b/backend/social/chat/consumers.py @@ -5,38 +5,147 @@ from account.models import UserProfile from channels.db import database_sync_to_async from channels.generic.websocket import AsyncWebsocketConsumer +from asgiref.sync import sync_to_async, async_to_sync class ChatConsumer(AsyncWebsocketConsumer): + # -- CONNECT -- async def connect(self): + self.chat_id = self.scope["url_route"]["kwargs"]["chat_id"] + self.chat_name = f"chat_{self.chat_id}" + user = self.scope["user"] if not user.is_authenticated: await self.close(code=4401) # unauthorized return + #join chat group + async_to_sync(self.channel_layer.group_add)( + self.chat_name, + ) + + + await self.accept() + # -- DISCONNECT -- async def disconnect(self, close_code): + async_to_sync(self.channel_layer.group_discard)( + self.chat_name + ) + self.disconnect() pass + # -- RECIVE -- async def receive(self, data): - if data["type"] == "chat_message": - pass + if data["type"] == "new_chat_message": + + message = data["message"] + + # Send message to room group + async_to_sync(self.channel_layer.group_send)( + self.chat_name, {"type": "chat.message", "message": message} + ) + + elif data["type"] == "new_reply_chat_message": + message = data["message"] + reply_to_id = data["reply_to_id"] + + # Send message to room group + async_to_sync(self.channel_layer.group_send)( + self.chat_name, {"type": "reply.chat.message", "message": message, "reply_to_id": reply_to_id} + ) + + elif data["type"] == "edit_chat_message": + message = data["message"] + + # Send message to room group + async_to_sync(self.channel_layer.group_send)( + self.chat_name, {"type": "edit.message", "message": message} + ) + + elif data["type"] == "delete_chat_message": + message_id = data["message_id"] + + # Send message to room group + async_to_sync(self.channel_layer.group_send)( + self.chat_name, {"type": "delete.message", "message_id": message_id} + ) + + elif data["type"] == "typing": + is_typing = data["is_typing"] + + # Send typing status to room group + async_to_sync(self.channel_layer.group_send)( + self.chat_name, {"type": "typing.status", "user": self.scope["user"].username, "is_typing": is_typing} + ) + + elif data["type"] == "stop_typing": + # Send stop typing status to room group + async_to_sync(self.channel_layer.group_send)( + self.chat_name, {"type": "stop.typing", "user": self.scope["user"].username} + ) + + elif data["type"] == "reaction": + message_id = data["message_id"] + emoji = data["emoji"] + + # Send reaction to room group + async_to_sync(self.channel_layer.group_send)( + self.chat_name, {"type": "message.reaction", "message_id": message_id, "emoji": emoji, "user": self.scope["user"].username} + ) + + elif data["type"] == "unreaction": + message_id = data["message_id"] + emoji = data["emoji"] + + # Send unreaction to room group + async_to_sync(self.channel_layer.group_send)( + self.chat_name, {"type": "message.unreaction", "message_id": message_id, "emoji": emoji, "user": self.scope["user"].username} + ) + else: self.close(reason="Unsupported message type") # -- CUSTOM METHODS -- - async def send_message_to_chat_group(self, event): + def send_message_to_chat_group(self, event): message = event["message"] - await create_new_message() - await self.send(text_data=json.dumps({"message": message})) + create_new_message() + self.send(text_data=json.dumps({"message": message})) + + def edit_message_in_chat_group(self, event): + message = event["message"] + self.send(text_data=json.dumps({"message": message})) +# -- MESSAGES -- +@database_sync_to_async +def create_new_message(): + return None + @database_sync_to_async -def create_new_message(user_id): +def create_new_reply_message(): + return None + +@database_sync_to_async +def edit_message(): + return None + +@database_sync_to_async +def delete_message(): + return None + + +# -- REACTIONS -- +@database_sync_to_async +def react_to_message(): + return None + +@database_sync_to_async +def unreact_to_message(): return None \ No newline at end of file diff --git a/backend/social/chat/models.py b/backend/social/chat/models.py index f8e3b79..d10404c 100644 --- a/backend/social/chat/models.py +++ b/backend/social/chat/models.py @@ -47,6 +47,15 @@ class Message(models.Model): on_delete=models.CASCADE, related_name='sent_messages' ) + + #odpověď na jinou zprávu + reply_to = models.ForeignKey( + 'self', + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='replies' + ) content = models.TextField(blank=True) diff --git a/backend/social/chat/routing.py b/backend/social/chat/routing.py index 4c48d8f..520fc15 100644 --- a/backend/social/chat/routing.py +++ b/backend/social/chat/routing.py @@ -4,5 +4,5 @@ from django.urls import re_path from . import consumers websocket_urlpatterns = [ - re_path(r"ws/chat/(?P\w+)/$", consumers.ChatConsumer.as_asgi()), + re_path(r"ws/chat/(?P\w+)/$", consumers.ChatConsumer.as_asgi()), ] \ No newline at end of file diff --git a/backend/social/chat/views.py b/backend/social/chat/views.py index 91ea44a..e653958 100644 --- a/backend/social/chat/views.py +++ b/backend/social/chat/views.py @@ -1,3 +1,25 @@ from django.shortcuts import render # Create your views here. + + +def get_users_chats(request): + return None + +def create_chat(request): + return None + +def invite_user_to_chat(request, chat_id: int, user_ids: list): + return None + +def delete_chat(request, chat_id: int): + return None + +def leave_chat(request, chat_id: int): + return None + +def edit_chat(request, chat_object): + return None + +def get_chat_messages(request, chat_id: int, limit: int = 50, offset: int = 0): + return None \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 72a74f2..7673480 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,10 +1,14 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import Home from "./pages/home/home"; import HomeLayout from "./layouts/HomeLayout"; +import ChatLayout from "./layouts/ChatLayout"; + import Downloader from "./pages/downloader/Downloader"; -import PrivateRoute from "./routes/PrivateRoute"; +import Home from "./pages/home/home"; import DroneServisSection from "./pages/home/components/Services/droneServis"; + +import PrivateRoute from "./routes/PrivateRoute"; + //import { UserContextProvider } from "./context/UserContext"; // Pages @@ -36,9 +40,10 @@ export default function App() { {/* Example protected route group (kept for future use) */} }> - }> - } /> + }> + + {/* */} diff --git a/frontend/src/layouts/ChatLayout.tsx b/frontend/src/layouts/ChatLayout.tsx new file mode 100644 index 0000000..706bd8f --- /dev/null +++ b/frontend/src/layouts/ChatLayout.tsx @@ -0,0 +1,21 @@ +import Footer from "../components/Footer/footer"; +import { Outlet } from "react-router"; +import SiteNav, { type User } from "../components/navbar/SiteNav"; + + +const userexists: User = { + username: "Bruno", + email: "", + avatarUrl: "", +}; + +import styles from "./HomeLayout.module.css"; + +export default function ChatLayout(){ + return( +
+ {}} onLogout={() => {}} /> + +
+ ) +} \ No newline at end of file diff --git a/frontend/src/pages/Chat/ChatPage.tsx b/frontend/src/pages/Chat/ChatPage.tsx new file mode 100644 index 0000000..e69de29