From af9595c447d98a906919e05575d2280ff11b6f48 Mon Sep 17 00:00:00 2001 From: Xamora Date: Thu, 30 Nov 2023 16:36:21 +0100 Subject: [PATCH 1/8] Don't merge, it's prototypal --- frontend/static/js/index.js | 4 +- frontend/static/js/views/Chat.js | 4 +- frontend/static/js/views/Search.js | 38 +++++++++++++++++++ .../static/js/views/accounts/RegisterView.js | 2 +- frontend/templates/index.html | 2 +- 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 frontend/static/js/views/Search.js diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index 47255df..d5bef31 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -3,6 +3,7 @@ import Dashboard from "./views/Dashboard.js"; import Posts from "./views/Posts.js"; import PostView from "./views/PostView.js"; import Settings from "./views/Settings.js"; +import Search from "./views/Search.js"; import Chat from "./views/Chat.js"; import HomeView from "./views/HomeView.js"; import RegisterView from "./views/accounts/RegisterView.js"; @@ -40,6 +41,7 @@ const router = async (uri = "") => { { path: "/login", view: LoginView }, { path: "/logout", view: LogoutView }, { path: "/register", view: RegisterView }, + { path: "/search", view: Search }, { path: "/chat", view: Chat }, { path: "/home", view: HomeView }, ]; @@ -94,4 +96,4 @@ document.addEventListener("DOMContentLoaded", () => { router(location.pathname); }); -export { client, navigateTo } \ No newline at end of file +export { client, navigateTo } diff --git a/frontend/static/js/views/Chat.js b/frontend/static/js/views/Chat.js index de4b02b..472be28 100644 --- a/frontend/static/js/views/Chat.js +++ b/frontend/static/js/views/Chat.js @@ -4,7 +4,7 @@ export default class extends AbstractAuthentifiedView { constructor(params) { super(params, "Chat"); - let url = `wss://${window.location.host}/ws/socket-server/` + let url = `ws://${window.location.host}/ws/socket-server/` this.chatSocket = new WebSocket(url) this.chatSocket.onmessage = function(e){ @@ -44,7 +44,7 @@ export default class extends AbstractAuthentifiedView {

Chat

- +
diff --git a/frontend/static/js/views/Search.js b/frontend/static/js/views/Search.js new file mode 100644 index 0000000..9a8bb52 --- /dev/null +++ b/frontend/static/js/views/Search.js @@ -0,0 +1,38 @@ +import AbstractAuthentifiedView from "./AbstractAuthentifiedView.js"; + +export default class extends AbstractAuthentifiedView { + constructor(params) { + super(params, "Search"); + } + + async postInit() { + let users = ["cramptéMan", "cacaMan", "chatteWomen"] + + let list_users = document.getElementById('list_users'); + for (const user of users) { + var new_user = document.createElement("li"); + new_user.appendChild(document.createTextNode(user)); + list_users.appendChild(new_user); + } + console.log(list_users); + } + + async leavePage() { + } + + async getHtml() { + return ` +

Search

+ +
+ +
+ +
+
    +
+
+ + `; + } +} diff --git a/frontend/static/js/views/accounts/RegisterView.js b/frontend/static/js/views/accounts/RegisterView.js index 5873b4c..f6c23ec 100644 --- a/frontend/static/js/views/accounts/RegisterView.js +++ b/frontend/static/js/views/accounts/RegisterView.js @@ -47,4 +47,4 @@ export default class extends AbstractAuthentifiedView {
`; } -} \ No newline at end of file +} diff --git a/frontend/templates/index.html b/frontend/templates/index.html index 36e1ff5..e6b8e37 100644 --- a/frontend/templates/index.html +++ b/frontend/templates/index.html @@ -14,7 +14,7 @@ Settings Login Register - Chat + Search
From cee188145daf4f44775d29dc4174681d6b212ef1 Mon Sep 17 00:00:00 2001 From: Xamora Date: Mon, 11 Dec 2023 10:53:34 +0100 Subject: [PATCH 2/8] no idea --- .gitignore | 2 +- frontend/static/js/api/client.js | 2 +- package-lock.json | 6 ++++++ trancendence/asgi.py | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index cafcea3..80d73fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .env *.pyc db.sqlite3 -**/migrations/** \ No newline at end of file +**/migrations/** diff --git a/frontend/static/js/api/client.js b/frontend/static/js/api/client.js index b0a5439..37c82d6 100644 --- a/frontend/static/js/api/client.js +++ b/frontend/static/js/api/client.js @@ -62,4 +62,4 @@ class Client } } -export {Client} \ No newline at end of file +export {Client} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c26bfb2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "ft_transcendence", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/trancendence/asgi.py b/trancendence/asgi.py index a229228..d0068aa 100644 --- a/trancendence/asgi.py +++ b/trancendence/asgi.py @@ -17,7 +17,7 @@ from django.core.asgi import get_asgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings') application = ProtocolTypeRouter({ - 'http':get_asgi_application(), + 'http':get_asgi_application(), 'websocket':AuthMiddlewareStack( URLRouter( chat.routing.websocket_urlpatterns From 08093627c9fa384690056f6f658fb7c351bec780 Mon Sep 17 00:00:00 2001 From: Xamora Date: Mon, 11 Dec 2023 13:09:20 +0100 Subject: [PATCH 3/8] =?UTF-8?q?Camille=20=C3=A0=20trop=20les=20crampt?= =?UTF-8?q?=C3=A9s,=20en=20plus=20il=20va=20voir=20francis=20=C3=A9bou?= =?UTF-8?q?=C3=A9=20qui=20est=20pass=C3=A9=20chez=20noz=20avec=20son=20dar?= =?UTF-8?q?on=20qui=20se=20dore=20la=20biscotte=20en=20guadeloupe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/static/js/views/Search.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/static/js/views/Search.js b/frontend/static/js/views/Search.js index 9a8bb52..fe0d7fe 100644 --- a/frontend/static/js/views/Search.js +++ b/frontend/static/js/views/Search.js @@ -1,4 +1,5 @@ import AbstractAuthentifiedView from "./AbstractAuthentifiedView.js"; +import {client} from "../index.js"; export default class extends AbstractAuthentifiedView { constructor(params) { @@ -6,6 +7,8 @@ export default class extends AbstractAuthentifiedView { } async postInit() { + let profiles = await client.profiles.all(); + console.log(profiles); let users = ["cramptéMan", "cacaMan", "chatteWomen"] let list_users = document.getElementById('list_users'); From c178556a2ed3a981b47a0b2423908d564d6bfea1 Mon Sep 17 00:00:00 2001 From: Xamora Date: Mon, 11 Dec 2023 14:54:39 +0100 Subject: [PATCH 4/8] Add functional research bar --- frontend/static/css/search.css | 17 +++++++++ frontend/static/js/views/Search.js | 56 +++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 frontend/static/css/search.css diff --git a/frontend/static/css/search.css b/frontend/static/css/search.css new file mode 100644 index 0000000..21e3e8f --- /dev/null +++ b/frontend/static/css/search.css @@ -0,0 +1,17 @@ +#app img +{ + max-height: 100px; + max-width: 100px; +} + +#app ul +{ + margin: 5px 0 0 0; + padding: 0 0 0 0; + list-style-type: none; +} + +#app li +{ + margin: 10px 10px 0 0; +} diff --git a/frontend/static/js/views/Search.js b/frontend/static/js/views/Search.js index fe0d7fe..36f6a44 100644 --- a/frontend/static/js/views/Search.js +++ b/frontend/static/js/views/Search.js @@ -1,35 +1,57 @@ -import AbstractAuthentifiedView from "./AbstractAuthentifiedView.js"; +import AbstractView from "./AbstractView.js"; import {client} from "../index.js"; -export default class extends AbstractAuthentifiedView { +export default class extends AbstractView { constructor(params) { super(params, "Search"); } async postInit() { - let profiles = await client.profiles.all(); - console.log(profiles); - let users = ["cramptéMan", "cacaMan", "chatteWomen"] - let list_users = document.getElementById('list_users'); - for (const user of users) { - var new_user = document.createElement("li"); - new_user.appendChild(document.createTextNode(user)); - list_users.appendChild(new_user); - } - console.log(list_users); + let search = document.getElementById("form"); + search.addEventListener("input", this.users) + + this.users(); + } - async leavePage() { + async users() { + + let search = document.getElementById("form").value; + + + let users = await client.profiles.all(); + let list_users = document.getElementById('list_users'); + list_users.innerHTML = ""; + + users.filter(user => user.username.startsWith(search) == true).forEach((user) => { + var new_user = document.createElement("li"); + + // username + let username = document.createElement("a"); + username.href = `/profiles/${user.user_id}`; + username.appendChild(document.createTextNode(user.username)); + new_user.appendChild(username); + + // break line + new_user.appendChild(document.createElement("br")); + + // avatar + var img = document.createElement("img"); + img.src = user.avatar_url; + new_user.appendChild(img); + + list_users.appendChild(new_user); + }); + console.log(list_users); + } async getHtml() { return ` -

Search

+ -
- -
+
    From 78379aea1b2dc41a9109d260f99f2d55f6459da4 Mon Sep 17 00:00:00 2001 From: Xamora Date: Mon, 11 Dec 2023 16:14:27 +0100 Subject: [PATCH 5/8] Bug fix with username in chat --- chat/consumers.py | 6 +++--- frontend/static/js/views/Search.js | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/chat/consumers.py b/chat/consumers.py index a1a6602..b0ef820 100644 --- a/chat/consumers.py +++ b/chat/consumers.py @@ -22,15 +22,15 @@ class ChatConsumer(WebsocketConsumer): self.room_group_name, { 'type':'chat_message', + 'username':self.scope["user"].username, 'message':message } ) def chat_message(self, event): - message = event['message'] self.send(text_data=json.dumps({ 'type':'chat', - 'username':self.scope["user"].username, - 'message':message + 'username':event['username'], + 'message':event['message'] })) diff --git a/frontend/static/js/views/Search.js b/frontend/static/js/views/Search.js index 36f6a44..4c1f4d4 100644 --- a/frontend/static/js/views/Search.js +++ b/frontend/static/js/views/Search.js @@ -19,6 +19,7 @@ export default class extends AbstractView { let search = document.getElementById("form").value; + let logged = client.isAuthentificate(); let users = await client.profiles.all(); let list_users = document.getElementById('list_users'); @@ -32,6 +33,15 @@ export default class extends AbstractView { username.href = `/profiles/${user.user_id}`; username.appendChild(document.createTextNode(user.username)); new_user.appendChild(username); + + // space + new_user.appendChild(document.createTextNode(" ")); + + // chat + let chat = document.createElement("a"); + chat.href = `/chat` + chat.appendChild(document.createTextNode("Chat")); + new_user.appendChild(chat); // break line new_user.appendChild(document.createElement("br")); @@ -41,6 +51,7 @@ export default class extends AbstractView { img.src = user.avatar_url; new_user.appendChild(img); + list_users.appendChild(new_user); }); console.log(list_users); From bc892bc15793c3b561f9d54393331ffad242aacc Mon Sep 17 00:00:00 2001 From: Xamora Date: Tue, 12 Dec 2023 10:04:46 +0100 Subject: [PATCH 6/8] Advance don't merge --- frontend/static/js/api/chat/channel.js | 9 +++++ frontend/static/js/api/chat/channels.js | 19 +++++++++ frontend/static/js/index.js | 2 - frontend/static/js/views/Chat.js | 4 +- frontend/static/js/views/Search.js | 23 ++++++++--- .../js/views/profiles/ProfilePageView.js | 2 +- .../static/js/views/profiles/ProfilesView.js | 40 ------------------- 7 files changed, 48 insertions(+), 51 deletions(-) create mode 100644 frontend/static/js/api/chat/channel.js create mode 100644 frontend/static/js/api/chat/channels.js delete mode 100644 frontend/static/js/views/profiles/ProfilesView.js diff --git a/frontend/static/js/api/chat/channel.js b/frontend/static/js/api/chat/channel.js new file mode 100644 index 0000000..d7d31ce --- /dev/null +++ b/frontend/static/js/api/chat/channel.js @@ -0,0 +1,9 @@ +class Channel { + constructor(id, members, messages) { + this.id = id; + this.members = members; + this.messages = messages; + } +} + +export {Channel} diff --git a/frontend/static/js/api/chat/channels.js b/frontend/static/js/api/chat/channels.js new file mode 100644 index 0000000..666d709 --- /dev/null +++ b/frontend/static/js/api/chat/channels.js @@ -0,0 +1,19 @@ +class Channels { + constructor(client) { + this.client = client; + } + + async createChannel(users_id) { + console.log(users_id); + let response = await this.client._post("/api/chat/", { + users_id:users_id + }); + + let data = await response.json(); + if (data == "Channel already exist") + return null; + return data; + } +} + +export {Channels} diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index b07362f..3db6fa5 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -11,7 +11,6 @@ import { Client } from "./api/client.js"; import AbstractRedirectView from "./views/AbstractRedirectView.js"; import MeView from "./views/MeView.js"; import ProfilePageView from "./views/profiles/ProfilePageView.js"; -import ProfilesView from "./views/profiles/ProfilesView.js"; let client = new Client(location.protocol + "//" + location.host) @@ -36,7 +35,6 @@ const navigateTo = async (uri) => { const router = async (uri) => { const routes = [ { path: "/", view: Dashboard }, - { path: "/profiles", view: ProfilesView}, { path: "/profiles/:id", view: ProfilePageView }, { path: "/settings", view: Settings }, { path: "/login", view: LoginView }, diff --git a/frontend/static/js/views/Chat.js b/frontend/static/js/views/Chat.js index 472be28..c84e2c2 100644 --- a/frontend/static/js/views/Chat.js +++ b/frontend/static/js/views/Chat.js @@ -1,6 +1,6 @@ -import AbstractAuthentifiedView from "./AbstractAuthentifiedView.js"; +import AbstractView from "./AbstractView.js"; -export default class extends AbstractAuthentifiedView { +export default class extends AbstractView { constructor(params) { super(params, "Chat"); diff --git a/frontend/static/js/views/Search.js b/frontend/static/js/views/Search.js index 4c1f4d4..883388f 100644 --- a/frontend/static/js/views/Search.js +++ b/frontend/static/js/views/Search.js @@ -19,7 +19,7 @@ export default class extends AbstractView { let search = document.getElementById("form").value; - let logged = client.isAuthentificate(); + let logged = await client.isAuthentificate(); let users = await client.profiles.all(); let list_users = document.getElementById('list_users'); @@ -38,10 +38,21 @@ export default class extends AbstractView { new_user.appendChild(document.createTextNode(" ")); // chat - let chat = document.createElement("a"); - chat.href = `/chat` - chat.appendChild(document.createTextNode("Chat")); - new_user.appendChild(chat); + if (logged) { + let chat = document.createElement("a"); + let array = [ + client.me.user_id, + user.user_id, + ]; + console.log(client.me.id); + chat.addEventListener("click", async function(){ + console.log("click"); + await client.channels.createChannel([client.me.user_id , user.user_id]); + }); + //chat.href = `/chat` + chat.appendChild(document.createTextNode("Chat")); + new_user.appendChild(chat); + } // break line new_user.appendChild(document.createElement("br")); @@ -54,7 +65,7 @@ export default class extends AbstractView { list_users.appendChild(new_user); }); - console.log(list_users); + //console.log(list_users); } diff --git a/frontend/static/js/views/profiles/ProfilePageView.js b/frontend/static/js/views/profiles/ProfilePageView.js index ffd9ee7..81247a4 100644 --- a/frontend/static/js/views/profiles/ProfilePageView.js +++ b/frontend/static/js/views/profiles/ProfilePageView.js @@ -26,4 +26,4 @@ export default class extends AbstractView { `; } -} \ No newline at end of file +} diff --git a/frontend/static/js/views/profiles/ProfilesView.js b/frontend/static/js/views/profiles/ProfilesView.js deleted file mode 100644 index d07284e..0000000 --- a/frontend/static/js/views/profiles/ProfilesView.js +++ /dev/null @@ -1,40 +0,0 @@ -import AbstractView from "../AbstractView.js"; -import { client } from "../../index.js" - -export default class extends AbstractView { - constructor(params) { - super(params, "Profiles"); - } - - async postInit() - { - let profiles = await client.profiles.all() - let profile_list_element = document.getElementById("profile-list") - - profiles.forEach((profile) => { - let profile_element = document.createElement("div"); - profile_element.className = "item"; - - let avatar = document.createElement("img"); - avatar.src = profile.avatar_url; - - let username = document.createElement("a"); - username.href = `/profiles/${profile.user_id}`; - username.appendChild(document.createTextNode(profile.username)); - - profile_element.appendChild(avatar); - profile_element.appendChild(username); - - profile_list_element.appendChild(profile_element) - }); - } - - async getHtml() { - return ` - -
    - -
    - `; - } -} \ No newline at end of file From 0e3b19fcd9d59cc15f67bffd657989806815cf63 Mon Sep 17 00:00:00 2001 From: Xamora Date: Tue, 12 Dec 2023 10:05:13 +0100 Subject: [PATCH 7/8] Advance don't merge --- chat/consumers.py | 10 +++++++++- chat/models.py | 14 ++++++++++++++ chat/serializers.py | 8 ++++++++ chat/urls.py | 9 +++++++++ chat/views.py | 30 ++++++++++++++++++++++++++++-- frontend/templates/index.html | 4 ++-- trancendence/settings.py | 3 ++- trancendence/urls.py | 3 ++- 8 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 chat/serializers.py create mode 100644 chat/urls.py diff --git a/chat/consumers.py b/chat/consumers.py index b0ef820..fb7bba5 100644 --- a/chat/consumers.py +++ b/chat/consumers.py @@ -18,17 +18,25 @@ class ChatConsumer(WebsocketConsumer): text_data_json = json.loads(text_data) message = text_data_json['message'] + user = self.scope["user"] + if user.is_anonymous: + return; + async_to_sync(self.channel_layer.group_send)( self.room_group_name, { 'type':'chat_message', - 'username':self.scope["user"].username, + 'username':user.username, 'message':message } ) def chat_message(self, event): + user = self.scope["user"] + if user.is_anonymous: + return; + self.send(text_data=json.dumps({ 'type':'chat', 'username':event['username'], diff --git a/chat/models.py b/chat/models.py index 71a8362..3479bc7 100644 --- a/chat/models.py +++ b/chat/models.py @@ -1,3 +1,17 @@ from django.db import models +from django.db.models import IntegerField +from django.contrib.auth.models import User # Create your models here. +class ChannelModel(models.Model): + pass + +class MemberModel(models.Model): + member_id = IntegerField(primary_key=False) + channel_id = IntegerField(primary_key=False) + +class MessageModel(models.Model): + channel_id = IntegerField(primary_key=False) + author_id = IntegerField(primary_key=False) + content = models.CharField(max_length=255) + time = models.DateField() diff --git a/chat/serializers.py b/chat/serializers.py new file mode 100644 index 0000000..2d62490 --- /dev/null +++ b/chat/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers +from .models import ChannelModel + +class ChannelSerializer(serializers.ModelSerializer): + + class Meta: + model = ChannelModel + fields = [] diff --git a/chat/urls.py b/chat/urls.py new file mode 100644 index 0000000..f9300cf --- /dev/null +++ b/chat/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from django.conf import settings +from django.conf.urls.static import static + +from . import views + +urlpatterns = [ + path("", views.ChatView.as_view(), name="chat_page"), +] diff --git a/chat/views.py b/chat/views.py index 91ea44a..51dc909 100644 --- a/chat/views.py +++ b/chat/views.py @@ -1,3 +1,29 @@ -from django.shortcuts import render +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import authentication, permissions +from rest_framework.authentication import SessionAuthentication +from .models import ChannelModel, MemberModel, MessageModel -# Create your views here. +class ChatView(APIView): + permission_classes = (permissions.IsAuthenticated,) + authentication_classes = (SessionAuthentication,) + + def post(self, request, pk): + if (ChannelModel.objects.filter(pk = pk)): + return Response("Channel already exist") + + data: dict = request.data + users_id = request.data.get("users_id", []) + if len(users_id) < 2: + return Response("Not enought members to create the channel") + + new_channel = ChannelModel() + new_channel.save() + + for user_id in users_id: + new_member = MemberModel() + new_member.channel_id = new_channel.pk + new_member.member_id = user_id + new_member.save() + + return Response("Channel created") diff --git a/frontend/templates/index.html b/frontend/templates/index.html index b6d4f58..70f227e 100644 --- a/frontend/templates/index.html +++ b/frontend/templates/index.html @@ -10,10 +10,10 @@
    diff --git a/trancendence/settings.py b/trancendence/settings.py index 4e89778..0ddacfc 100644 --- a/trancendence/settings.py +++ b/trancendence/settings.py @@ -46,6 +46,7 @@ INSTALLED_APPS = [ 'accounts.apps.AccountsConfig', 'profiles.apps.ProfilesConfig', 'frontend.apps.FrontendConfig', + 'chat.apps.ChatConfig', 'corsheaders', 'rest_framework', @@ -148,4 +149,4 @@ STATIC_URL = 'static/' # Default primary key field type # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/trancendence/urls.py b/trancendence/urls.py index 0f136f1..46a790f 100644 --- a/trancendence/urls.py +++ b/trancendence/urls.py @@ -21,5 +21,6 @@ urlpatterns = [ path('admin/', admin.site.urls), path('api/profiles/', include('profiles.urls')), path('api/accounts/', include('accounts.urls')), + path('api/chat/', include('chat.urls')), path('', include('frontend.urls')), -] \ No newline at end of file +] From bcb072f7d9f5f7817d949837219231232160ff11 Mon Sep 17 00:00:00 2001 From: Xamora Date: Fri, 15 Dec 2023 20:32:43 +0100 Subject: [PATCH 8/8] chat functional --- README.md | 8 +- accounts/views/logged.py | 2 +- accounts/views/register.py | 2 +- chat/admin.py | 5 +- chat/consumers.py | 61 +++++++-- chat/models.py | 9 +- chat/routing.py | 2 +- chat/urls.py | 1 + chat/views.py | 81 +++++++++++- frontend/static/css/search.css | 53 ++++++++ frontend/static/js/api/chat/channel.js | 76 ++++++++++- frontend/static/js/api/chat/channels.js | 26 +++- frontend/static/js/api/chat/message.js | 10 ++ frontend/static/js/api/client.js | 4 + frontend/static/js/views/Chat.js | 2 +- frontend/static/js/views/Search.js | 165 +++++++++++++++++++++--- 16 files changed, 456 insertions(+), 51 deletions(-) create mode 100644 frontend/static/js/api/chat/message.js diff --git a/README.md b/README.md index 69e2f03..846825e 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,15 @@ pip install -r requirements.txt - Setup database ``` python manage.py runserver makemigrations profiles -python manage.py migrate profiles +python manage.py runserver makemigrations chat +python manage.py migrate ``` - Start the developpement server ``` python manage.py runserver 0.0.0.0:8000 ``` + +coc nvim +``` +pip install django-stubs +``` diff --git a/accounts/views/logged.py b/accounts/views/logged.py index 8e2fecb..7deff05 100644 --- a/accounts/views/logged.py +++ b/accounts/views/logged.py @@ -15,4 +15,4 @@ class LoggedView(APIView): def get(self, request: HttpRequest): if (request.user.is_authenticated): return Response({'id': request.user.pk}, status=status.HTTP_200_OK) - return Response('false', status=status.HTTP_200_OK) \ No newline at end of file + return Response('false', status=status.HTTP_200_OK) diff --git a/accounts/views/register.py b/accounts/views/register.py index ee5724a..667ef2a 100644 --- a/accounts/views/register.py +++ b/accounts/views/register.py @@ -15,4 +15,4 @@ class RegisterView(APIView): if user: login(request, user) return Response("user created", status=status.HTTP_201_CREATED) - return Response(status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return Response(status=status.HTTP_400_BAD_REQUEST) diff --git a/chat/admin.py b/chat/admin.py index 8c38f3f..8f38a1d 100644 --- a/chat/admin.py +++ b/chat/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin +from .models import ChannelModel, MemberModel, MessageModel -# Register your models here. +admin.site.register(ChannelModel) +admin.site.register(MemberModel) +admin.site.register(MessageModel) diff --git a/chat/consumers.py b/chat/consumers.py index fb7bba5..d1c33d3 100644 --- a/chat/consumers.py +++ b/chat/consumers.py @@ -1,10 +1,24 @@ import json from channels.generic.websocket import WebsocketConsumer from asgiref.sync import async_to_sync +from .models import MemberModel, MessageModel +import time class ChatConsumer(WebsocketConsumer): def connect(self): - self.room_group_name = 'test' + channel_id : str = self.scope['path'].split('/')[3] + + self.room_group_name = 'chat' + channel_id + + user = self.scope["user"] + if (user.is_anonymous or not user.is_authenticated): + return + + if MemberModel.objects.filter(member_id=user.pk, channel_id=int(channel_id)).count() != 1: + return + + if (self.channel_layer == None): + return async_to_sync(self.channel_layer.group_add)( self.room_group_name, @@ -14,31 +28,58 @@ class ChatConsumer(WebsocketConsumer): self.accept() - def receive(self, text_data): + def receive(self, text_data=None, bytes_data=None): + if text_data == None: + return + text_data_json = json.loads(text_data) message = text_data_json['message'] + channel_id : int = int(self.scope['path'].split('/')[3]) user = self.scope["user"] - if user.is_anonymous: - return; + if (user.is_anonymous or not user.is_authenticated): + return + if MemberModel.objects.filter(member_id=user.pk, channel_id=channel_id).count() != 1: + return + + if (self.channel_layer == None): + return + + print(message) + + message_time : int = int(time.time() * 1000) async_to_sync(self.channel_layer.group_send)( self.room_group_name, { 'type':'chat_message', - 'username':user.username, - 'message':message + 'author_id':user.pk, + 'content':message, + 'time':message_time, } ) + + new_message = MessageModel() + new_message.channel_id = channel_id + new_message.author_id = user.pk + new_message.content = message + new_message.time = message_time + new_message.save() + def chat_message(self, event): + channel_id : int = int(self.scope['path'].split('/')[3]) user = self.scope["user"] - if user.is_anonymous: - return; + if (user.is_anonymous or not user.is_authenticated): + return + + if MemberModel.objects.filter(member_id=user.pk, channel_id=channel_id).count() != 1: + return self.send(text_data=json.dumps({ 'type':'chat', - 'username':event['username'], - 'message':event['message'] + 'author_id':event['author_id'], + 'content':event['content'], + 'time': event['time'], })) diff --git a/chat/models.py b/chat/models.py index 3479bc7..236c4ee 100644 --- a/chat/models.py +++ b/chat/models.py @@ -1,6 +1,7 @@ from django.db import models from django.db.models import IntegerField from django.contrib.auth.models import User +from django.contrib import admin # Create your models here. class ChannelModel(models.Model): @@ -10,8 +11,14 @@ class MemberModel(models.Model): member_id = IntegerField(primary_key=False) channel_id = IntegerField(primary_key=False) + def __str__(self): + return "member_id: " + str(self.member_id) + ", channel_id: " + str(self.channel_id) + class MessageModel(models.Model): channel_id = IntegerField(primary_key=False) author_id = IntegerField(primary_key=False) content = models.CharField(max_length=255) - time = models.DateField() + time = IntegerField(primary_key=False) + + def __str__(self): + return "author_id: " + str(self.author_id) + ", channel_id: " + str(self.channel_id) + ", content: " + self.content diff --git a/chat/routing.py b/chat/routing.py index 5df8609..c799d8a 100644 --- a/chat/routing.py +++ b/chat/routing.py @@ -2,5 +2,5 @@ from django.urls import re_path from . import consumers websocket_urlpatterns = [ - re_path(r'ws/socket-server/', consumers.ChatConsumer.as_asgi()) + re_path(r'ws/chat/(?P\d+)/$', consumers.ChatConsumer.as_asgi()) ] diff --git a/chat/urls.py b/chat/urls.py index f9300cf..f6a905e 100644 --- a/chat/urls.py +++ b/chat/urls.py @@ -6,4 +6,5 @@ from . import views urlpatterns = [ path("", views.ChatView.as_view(), name="chat_page"), + path("", views.ChatsView.as_view(), name="chats_page"), ] diff --git a/chat/views.py b/chat/views.py index 51dc909..9e8a655 100644 --- a/chat/views.py +++ b/chat/views.py @@ -1,29 +1,100 @@ from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework import authentication, permissions +from rest_framework import authentication, permissions, status from rest_framework.authentication import SessionAuthentication from .models import ChannelModel, MemberModel, MessageModel +from django.core import serializers class ChatView(APIView): permission_classes = (permissions.IsAuthenticated,) authentication_classes = (SessionAuthentication,) + def get(self, request, pk): + if (ChannelModel.objects.filter(pk=pk)): + return Response({'channel_id': pk}) + else: + return Response("Channel doesn't exist") + + def delete(self, request, pk): + + ChannelModel.objects.filter(pk=pk).delete() + MessageModel.objects.filter(pk=pk).delete() + MemberModel.objects.filter(pk=pk).delete() + + return Response({'channel_id': pk}) + + """ def post(self, request, pk): - if (ChannelModel.objects.filter(pk = pk)): - return Response("Channel already exist") + channel = ChannelModel.objects.filter(pk=pk) + message = request.data.get("message", []) + print(message) + if (message == []): + return Response('No message', status=status.HTTP_400_BAD_REQUEST) + + new_message = MessageModel() + new_message.channel_id = message["channel_id"] + new_message.author_id = message["author_id"] + new_message.content = message["content"] + new_message.time = message["time"] + new_message.save() + + messages = MessageModel.objects.filter(channel_id=pk) + messages = serializers.serialize("json", messages) + return Response({'messages':messages}, status=status.HTTP_200_OK) + """ + + +class ChatsView(APIView): + def post(self, request): data: dict = request.data users_id = request.data.get("users_id", []) if len(users_id) < 2: - return Response("Not enought members to create the channel") + return Response('Not enought members to create the channel', status=status.HTTP_400_BAD_REQUEST) + if users_id[0] == users_id[1]: + return Response('Same member', status=status.HTTP_400_BAD_REQUEST) + for user_id1 in users_id: + for member1 in MemberModel.objects.filter(member_id=user_id1): + for user_id2 in users_id: + if user_id1 == user_id2: + continue + for member2 in MemberModel.objects.filter(member_id=user_id2): + if (member1.channel_id == member2.channel_id): + messages = MessageModel.objects.filter(channel_id=member1.channel_id) + messages = serializers.serialize("json", messages) + return Response({'channel_id': member1.channel_id, 'messages':messages}, status=status.HTTP_200_OK) + new_channel = ChannelModel() new_channel.save() for user_id in users_id: + new_member = MemberModel() new_member.channel_id = new_channel.pk new_member.member_id = user_id new_member.save() - return Response("Channel created") + return Response({'channel_id': new_channel.pk}, status=status.HTTP_201_CREATED) + + def delete(self, request): + + data: dict = request.data + users_id = request.data.get("users_id", []) + + #print(list(MemberModel.objects.all())) + + for user_id1 in users_id: + for member1 in MemberModel.objects.filter(member_id=user_id1): + for user_id2 in users_id: + if user_id1 == user_id2: + break + for member2 in MemberModel.objects.filter(member_id=user_id2): + if (member1.channel_id == member2.channel_id): + MessageModel.objects.filter(channel_id=member1.channel_id).delete() + member1.delete() + member2.delete() + ChannelModel.objects.get(pk=member1.channel_id).delete() + return Response("Channel removed", status=status.HTTP_200_OK) + + return Response("Channel doesn't exist", status=status.HTTP_404_NOT_FOUND) diff --git a/frontend/static/css/search.css b/frontend/static/css/search.css index 21e3e8f..50cd3a1 100644 --- a/frontend/static/css/search.css +++ b/frontend/static/css/search.css @@ -15,3 +15,56 @@ { margin: 10px 10px 0 0; } + +#app #chats { + overflow: hidden; + display: flex; + text-align: left; +} +#app #users { + margin-right: 50px; +} + +#app #chat { + position: relative; + height: 100%; + width: 60%; + /*border: 4px solid green;*/ + overflow:hidden; + /*overflow-y: scroll; + overflow-x: hidden;*/ +} + +#app #input_chat{ + position: sticky; + bottom: 0; + /*width: calc(100% - 8px);*/ + width: 100%; + border: none; + outline: none; + border-bottom: 2px solid green; + caret-color: green; +} + +#app #you { + text-align: left; + position: relative; + max-width: 48%; + left: 10px; + margin: 5px 0 0 0; + color: green; + + word-wrap: break-word; +} + +#app #other { + text-align: right; + position: relative; + max-width: 48%; + margin: 5px 0 0 auto; + right: 10px; + color: red; + + /* permet le retour à la ligne à la place de dépasser*/ + word-wrap: break-word; +} diff --git a/frontend/static/js/api/chat/channel.js b/frontend/static/js/api/chat/channel.js index d7d31ce..13c3db6 100644 --- a/frontend/static/js/api/chat/channel.js +++ b/frontend/static/js/api/chat/channel.js @@ -1,9 +1,77 @@ +import {Message} from "./message.js" + class Channel { - constructor(id, members, messages) { - this.id = id; - this.members = members; - this.messages = messages; + constructor(client, channel_id, members_id, messages, reload) { + this.client = client; + this.channel_id = channel_id; + this.members_id = members_id; + this.messages = []; + if (messages != undefined) + this.updateMessages(messages); + + this.connect(reload); } + + // reload = function to use when we receive a message + async connect(reload) { + let url = `ws://${window.location.host}/ws/chat/${this.channel_id}/`; + + this.chatSocket = new WebSocket(url); + this.chatSocket.onmessage = (event) =>{ + let data = JSON.parse(event.data) + + this.messages.push(new Message( + this.channel_id, + data.author_id, + data.content, + data.time, + )); + + reload(); + }; + } + + async disconnect() { + this.chatSocket.close(); + } + + updateMessages(messages) { + messages = JSON.parse(messages); + let new_messages = []; + + messages.forEach((message) => { + message = message["fields"]; + new_messages.push(new Message( + message["channel_id"], + message["author_id"], + message["content"], + message["time"], + )); + }); + + //console.log(new_messages); + this.messages = new_messages; + return new_messages; + } + + async sendMessageChannel(message) { + + if (this.chatSocket == undefined) + return; + + this.chatSocket.send(JSON.stringify({ + 'message':message + })); + } + + async deleteChannel() { + let response = await this.client._delete("/api/chat/" + this.channel_id, { + }); + + let data = await response.json(); + return data; + } + } export {Channel} diff --git a/frontend/static/js/api/chat/channels.js b/frontend/static/js/api/chat/channels.js index 666d709..afafba0 100644 --- a/frontend/static/js/api/chat/channels.js +++ b/frontend/static/js/api/chat/channels.js @@ -1,19 +1,37 @@ +import {Channel} from "./channel.js" +import {Message} from "./message.js" + class Channels { constructor(client) { this.client = client; } - async createChannel(users_id) { - console.log(users_id); + async createChannel(users_id, reload) { let response = await this.client._post("/api/chat/", { users_id:users_id }); let data = await response.json(); - if (data == "Channel already exist") - return null; + let exit_code = await response.status; + if (exit_code >= 300) + return undefined; + + let messages = undefined; + if (exit_code == 200) + messages = data.messages; + return new Channel(this.client, data.channel_id, users_id, messages, reload); + } + + async deleteChannel(users_id) { + let response = await this.client._delete("/api/chat/", { + users_id:users_id + }); + + let data = await response.json(); + console.log(response.status) return data; } + } export {Channels} diff --git a/frontend/static/js/api/chat/message.js b/frontend/static/js/api/chat/message.js new file mode 100644 index 0000000..4106512 --- /dev/null +++ b/frontend/static/js/api/chat/message.js @@ -0,0 +1,10 @@ +class Message { + constructor(channel_id, author_id, content, time) { + this.channel_id = channel_id; + this.author_id = author_id; + this.content = content; + this.time = time; + } +} + +export {Message} diff --git a/frontend/static/js/api/client.js b/frontend/static/js/api/client.js index 90fc88e..b8d9700 100644 --- a/frontend/static/js/api/client.js +++ b/frontend/static/js/api/client.js @@ -1,6 +1,7 @@ import { Account } from "./account.js"; import { Profile } from "./profile.js"; import { Profiles } from "./profiles.js"; +import { Channels } from './chat/channels.js'; function getCookie(name) { @@ -20,6 +21,9 @@ class Client this.account = new Account(this); this.profiles = new Profiles(this); this._logged = undefined; + + this.channels = new Channels(this); + this.channel = undefined; } async isAuthentificate() diff --git a/frontend/static/js/views/Chat.js b/frontend/static/js/views/Chat.js index c84e2c2..becd31f 100644 --- a/frontend/static/js/views/Chat.js +++ b/frontend/static/js/views/Chat.js @@ -4,7 +4,7 @@ export default class extends AbstractView { constructor(params) { super(params, "Chat"); - let url = `ws://${window.location.host}/ws/socket-server/` + let url = `ws://${window.location.host}/ws/chat/` this.chatSocket = new WebSocket(url) this.chatSocket.onmessage = function(e){ diff --git a/frontend/static/js/views/Search.js b/frontend/static/js/views/Search.js index 883388f..828aa0c 100644 --- a/frontend/static/js/views/Search.js +++ b/frontend/static/js/views/Search.js @@ -1,5 +1,6 @@ import AbstractView from "./AbstractView.js"; import {client} from "../index.js"; +import {Message} from "../api/chat/message.js" export default class extends AbstractView { constructor(params) { @@ -8,16 +9,20 @@ export default class extends AbstractView { async postInit() { - let search = document.getElementById("form"); + let search = document.getElementById("input_user"); search.addEventListener("input", this.users) + let chat_input = document.getElementById("input_chat"); + //chat_input.addEventListener("keydown", this.chat_manager) + this.users(); + this.chat(); } async users() { - let search = document.getElementById("form").value; + let search = document.getElementById("input_user").value.toLowerCase(); let logged = await client.isAuthentificate(); @@ -25,7 +30,7 @@ export default class extends AbstractView { let list_users = document.getElementById('list_users'); list_users.innerHTML = ""; - users.filter(user => user.username.startsWith(search) == true).forEach((user) => { + users.filter(user => user.username.toLowerCase().startsWith(search) == true).forEach((user) => { var new_user = document.createElement("li"); // username @@ -38,20 +43,41 @@ export default class extends AbstractView { new_user.appendChild(document.createTextNode(" ")); // chat - if (logged) { - let chat = document.createElement("a"); - let array = [ - client.me.user_id, - user.user_id, - ]; - console.log(client.me.id); - chat.addEventListener("click", async function(){ - console.log("click"); - await client.channels.createChannel([client.me.user_id , user.user_id]); + if (logged && client.me.user_id != user.user_id) { + let add_chat = document.createElement("a"); + add_chat.addEventListener("click", async () => { + if (client.channel != undefined) { + client.channel.members_id.forEach((member_id) => { + if (member_id == user.user_id) + client.channel = undefined; + }); + + if (client.channel == undefined) + return this.hideChat(); + + client.channel.disconnect(); + } + + client.channel = await client.channels.createChannel([client.me.user_id , user.user_id], this.chat); + this.chat(); }); - //chat.href = `/chat` - chat.appendChild(document.createTextNode("Chat")); - new_user.appendChild(chat); + add_chat.appendChild(document.createTextNode("Chat")); + new_user.appendChild(add_chat); + + /*new_user.appendChild(document.createTextNode(" ")); + + let remove = document.createElement("a"); + remove.addEventListener("click", async () => { + if (client.me.user_id != user.user_id) { + await client.channels.deleteChannel([client.me.user_id , user.user_id]); + if + client.channel.disconnect(); + client.channel = undefined; + this.chat() + } + }); + remove.appendChild(document.createTextNode("Remove")); + new_user.appendChild(remove);*/ } // break line @@ -69,15 +95,112 @@ export default class extends AbstractView { } + async chat() { + + let logged = await client.isAuthentificate(); + let reload = document.getElementById("messages"); + if (reload != null) + reload.remove(); + + reload = document.getElementById("members"); + if (reload != null) + reload.remove(); + + if (client.channel == undefined || !logged) + return ; + + let chats = document.getElementById("chats"); + + if (document.getElementById("chat") == null) { + let chat = document.createElement("div"); + chat.id = "chat"; + chats.appendChild(chat); + } + + // div des messages + let messages = document.createElement("div"); + messages.id = "messages"; + if (document.getElementById("input_chat") == null) + chat.appendChild(messages); + else + document.getElementById("input_chat").before(messages); + + // Input pour rentrer un message + if (document.getElementById("input_chat") == null) { + let chat_input = document.createElement("input"); + chat_input.id="input_chat"; + chat_input.type="text"; + chat_input.name="message"; + chat_input.placeholder="message bozo"; + chat_input.maxLength=255; + chat.appendChild(chat_input); + + chat_input.addEventListener("keydown", async () => { + if (event.keyCode == 13 && client.channel != undefined) { + //let chat_input = document.getElementById("input_chat"); + let chat_text = chat_input.value; + + await client.channel.sendMessageChannel(chat_text) + + // Reset + chat_input.value = ""; + } + }); + } + + // nom des membres du chat + let users = await client.profiles.all(); + let members = document.createElement("h2"); + members.id = "members"; + let usernames = ""; + client.channel.members_id.forEach((member_id) => { + if (member_id != client.me.user_id) { + if (usernames.length > 0) + usernames += ", "; + usernames += (users.filter(user => user.user_id == member_id)[0].username); + } + }); + members.appendChild(document.createTextNode(usernames)); + messages.before(members); + + // les messages + client.channel.messages.forEach((message) => { + let text = document.createElement("p"); + text.appendChild(document.createTextNode(message.content)); + if (message.author_id == client.me.user_id) + text.id = "you"; + else + text.id = "other"; + + messages.appendChild(text); + }); + + } + + async hideChat() { + + let close = document.getElementById("chat"); + if (close != null) + close.remove(); + + } + + async leavePage() { + if (client.channel != undefined) + client.channel.disconnect(); + client.channel = undefined + } + async getHtml() { return ` - - -
    -
      -
    +
    +
    + +
      +
    +
    `;