13 Commits

Author SHA1 Message Date
bcb072f7d9 chat functional 2023-12-15 20:32:43 +01:00
7e2c29e78b merge with server 2023-12-12 10:37:44 +01:00
0e3b19fcd9 Advance don't merge 2023-12-12 10:05:13 +01:00
bc892bc157 Advance don't merge 2023-12-12 10:04:46 +01:00
9b523f082f merge with server 2023-12-11 16:15:21 +01:00
78379aea1b Bug fix with username in chat 2023-12-11 16:14:27 +01:00
624fb47e04 merge with server 2023-12-11 14:55:17 +01:00
c178556a2e Add functional research bar 2023-12-11 14:54:39 +01:00
cb5affab48 merge with server 2023-12-11 13:10:17 +01:00
08093627c9 Camille à trop les cramptés, en plus il va voir francis éboué qui est passé chez noz avec son daron qui se dore la biscotte en guadeloupe 2023-12-11 13:09:20 +01:00
208dd206ce merge with server 2023-12-11 10:57:51 +01:00
cee188145d no idea 2023-12-11 10:53:34 +01:00
af9595c447 Don't merge, it's prototypal 2023-11-30 16:36:21 +01:00
44 changed files with 639 additions and 254 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@
db.sqlite3 db.sqlite3
**/migrations/** **/migrations/**
/profiles/static/avatars/* /profiles/static/avatars/*
!/profiles/static/avatars/default !/profiles/static/avatars/default.env

View File

@ -22,10 +22,16 @@ pip install -r requirements.txt
``` ```
- Setup database - Setup database
``` ```
python manage.py runserver makemigrations profiles games python manage.py runserver makemigrations profiles
python manage.py migrate profiles games python manage.py runserver makemigrations chat
python manage.py migrate
``` ```
- Start the developpement server - Start the developpement server
``` ```
python manage.py runserver 0.0.0.0:8000 python manage.py runserver 0.0.0.0:8000
``` ```
coc nvim
```
pip install django-stubs
```

View File

@ -15,4 +15,4 @@ class LoggedView(APIView):
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
if (request.user.is_authenticated): if (request.user.is_authenticated):
return Response({'id': request.user.pk}, status=status.HTTP_200_OK) return Response({'id': request.user.pk}, status=status.HTTP_200_OK)
return Response('false', status=status.HTTP_200_OK) return Response('false', status=status.HTTP_200_OK)

View File

@ -15,4 +15,4 @@ class RegisterView(APIView):
if user: if user:
login(request, user) login(request, user)
return Response("user created", status=status.HTTP_201_CREATED) return Response("user created", status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_400_BAD_REQUEST)

View File

@ -1,3 +1,6 @@
from django.contrib import admin 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)

View File

@ -1,10 +1,24 @@
import json import json
from channels.generic.websocket import WebsocketConsumer from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from .models import MemberModel, MessageModel
import time
class ChatConsumer(WebsocketConsumer): class ChatConsumer(WebsocketConsumer):
def connect(self): 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)( async_to_sync(self.channel_layer.group_add)(
self.room_group_name, self.room_group_name,
@ -14,23 +28,58 @@ class ChatConsumer(WebsocketConsumer):
self.accept() 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) text_data_json = json.loads(text_data)
message = text_data_json['message'] message = text_data_json['message']
channel_id : int = int(self.scope['path'].split('/')[3])
user = self.scope["user"]
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)( async_to_sync(self.channel_layer.group_send)(
self.room_group_name, self.room_group_name,
{ {
'type':'chat_message', 'type':'chat_message',
'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): def chat_message(self, event):
message = event['message']
channel_id : int = int(self.scope['path'].split('/')[3])
user = self.scope["user"]
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({ self.send(text_data=json.dumps({
'type':'chat', 'type':'chat',
'username':self.scope["user"].username, 'author_id':event['author_id'],
'message':message 'content':event['content'],
'time': event['time'],
})) }))

View File

@ -1,3 +1,24 @@
from django.db import models 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. # 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)
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 = IntegerField(primary_key=False)
def __str__(self):
return "author_id: " + str(self.author_id) + ", channel_id: " + str(self.channel_id) + ", content: " + self.content

View File

@ -2,5 +2,5 @@ from django.urls import re_path
from . import consumers from . import consumers
websocket_urlpatterns = [ websocket_urlpatterns = [
re_path(r'ws/socket-server/', consumers.ChatConsumer.as_asgi()) re_path(r'ws/chat/(?P<chat_id>\d+)/$', consumers.ChatConsumer.as_asgi())
] ]

8
chat/serializers.py Normal file
View File

@ -0,0 +1,8 @@
from rest_framework import serializers
from .models import ChannelModel
class ChannelSerializer(serializers.ModelSerializer):
class Meta:
model = ChannelModel
fields = []

10
chat/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from . import views
urlpatterns = [
path("<int:pk>", views.ChatView.as_view(), name="chat_page"),
path("", views.ChatsView.as_view(), name="chats_page"),
]

View File

@ -1,3 +1,100 @@
from django.shortcuts import render from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions, status
from rest_framework.authentication import SessionAuthentication
from .models import ChannelModel, MemberModel, MessageModel
from django.core import serializers
# Create your views here. 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):
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', 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_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)

View File

@ -0,0 +1,70 @@
#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;
}
#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;
}

View File

@ -0,0 +1,77 @@
import {Message} from "./message.js"
class Channel {
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}

View File

@ -0,0 +1,37 @@
import {Channel} from "./channel.js"
import {Message} from "./message.js"
class Channels {
constructor(client) {
this.client = client;
}
async createChannel(users_id, reload) {
let response = await this.client._post("/api/chat/", {
users_id:users_id
});
let data = await response.json();
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}

View File

@ -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}

View File

@ -1,7 +1,7 @@
import { Account } from "./account.js"; import { Account } from "./account.js";
import { MatchMaking } from "./matchmaking.js";
import { Profile } from "./profile.js"; import { Profile } from "./profile.js";
import { Profiles } from "./profiles.js"; import { Profiles } from "./profiles.js";
import { Channels } from './chat/channels.js';
function getCookie(name) function getCookie(name)
{ {
@ -20,8 +20,10 @@ class Client
this._url = url; this._url = url;
this.account = new Account(this); this.account = new Account(this);
this.profiles = new Profiles(this); this.profiles = new Profiles(this);
this.matchmaking = new MatchMaking(this);
this._logged = undefined; this._logged = undefined;
this.channels = new Channels(this);
this.channel = undefined;
} }
async isAuthentificate() async isAuthentificate()
@ -124,4 +126,4 @@ class Client
} }
} }
export {Client} export {Client}

View File

@ -1,42 +0,0 @@
import { client, navigateTo } from "../index.js"
class MatchMaking
{
/**
* @param {client} client
*/
constructor(client)
{
/**
* @type {client}
*/
this.client = client
}
async start(func)
{
if (!await this.client.isAuthentificate())
return null;
console.log(func)
this.callback = func
console.log(this.callback)
let url = `wss://${window.location.host}/ws/matchmaking/`;
this._chatSocket = new WebSocket(url);
this._chatSocket.onmessage = function (event) {
const data = JSON.parse(event.data);
console.log(func, data)
func(data.game_id)
};
}
async stop()
{
this._chatSocket.close()
}
}
export {MatchMaking}

View File

@ -1,6 +1,7 @@
import LoginView from "./views/accounts/LoginView.js"; import LoginView from "./views/accounts/LoginView.js";
import Dashboard from "./views/Dashboard.js"; import Dashboard from "./views/Dashboard.js";
import Settings from "./views/Settings.js"; import Settings from "./views/Settings.js";
import Search from "./views/Search.js";
import Chat from "./views/Chat.js"; import Chat from "./views/Chat.js";
import HomeView from "./views/HomeView.js"; import HomeView from "./views/HomeView.js";
import RegisterView from "./views/accounts/RegisterView.js"; import RegisterView from "./views/accounts/RegisterView.js";
@ -10,8 +11,6 @@ import { Client } from "./api/client.js";
import AbstractRedirectView from "./views/AbstractRedirectView.js"; import AbstractRedirectView from "./views/AbstractRedirectView.js";
import MeView from "./views/MeView.js"; import MeView from "./views/MeView.js";
import ProfilePageView from "./views/profiles/ProfilePageView.js"; import ProfilePageView from "./views/profiles/ProfilePageView.js";
import ProfilesView from "./views/profiles/ProfilesView.js";
import MatchMakingView from "./views/MatchMakingView.js";
let client = new Client(location.protocol + "//" + location.host) let client = new Client(location.protocol + "//" + location.host)
@ -36,16 +35,15 @@ const navigateTo = async (uri) => {
const router = async (uri) => { const router = async (uri) => {
const routes = [ const routes = [
{ path: "/", view: Dashboard }, { path: "/", view: Dashboard },
{ path: "/profiles", view: ProfilesView},
{ path: "/profiles/:id", view: ProfilePageView }, { path: "/profiles/:id", view: ProfilePageView },
{ path: "/settings", view: Settings }, { path: "/settings", view: Settings },
{ path: "/login", view: LoginView }, { path: "/login", view: LoginView },
{ path: "/logout", view: LogoutView }, { path: "/logout", view: LogoutView },
{ path: "/register", view: RegisterView }, { path: "/register", view: RegisterView },
{ path: "/search", view: Search },
{ path: "/chat", view: Chat }, { path: "/chat", view: Chat },
{ path: "/home", view: HomeView }, { path: "/home", view: HomeView },
{ path: "/me", view: MeView }, { path: "/me", view: MeView },
{ path: "/matchmaking", view: MatchMakingView },
]; ];
// Test each route for potential match // Test each route for potential match
@ -98,4 +96,4 @@ document.addEventListener("DOMContentLoaded", () => {
router(location.pathname); router(location.pathname);
}); });
export { client, navigateTo } export { client, navigateTo }

View File

@ -1,10 +1,10 @@
import AbstractAuthentifiedView from "./AbstractAuthentifiedView.js"; import AbstractView from "./AbstractView.js";
export default class extends AbstractAuthentifiedView { export default class extends AbstractView {
constructor(params) { constructor(params) {
super(params, "Chat"); super(params, "Chat");
let url = `wss://${window.location.host}/ws/socket-server/` let url = `ws://${window.location.host}/ws/chat/`
this.chatSocket = new WebSocket(url) this.chatSocket = new WebSocket(url)
this.chatSocket.onmessage = function(e){ this.chatSocket.onmessage = function(e){
@ -44,7 +44,7 @@ export default class extends AbstractAuthentifiedView {
<h1>Chat</h1> <h1>Chat</h1>
<form id="form"> <form id="form">
<input type="text" name="message" /> <input type="text" name="message" placeholder="message"/>
</form> </form>
<div id="messages"> <div id="messages">

View File

@ -9,7 +9,6 @@ export default class extends AbstractAuthentificateView {
async getHtml() { async getHtml() {
return ` return `
<h1>HOME</h1> <h1>HOME</h1>
<a href="/matchmaking" class="nav__link" data-link>Play a game</a>
<a href="/me" class="nav__link" data-link>Me</a> <a href="/me" class="nav__link" data-link>Me</a>
<a href="/logout" class="nav__link" data-link>Logout</a> <a href="/logout" class="nav__link" data-link>Logout</a>
`; `;

View File

@ -1,30 +0,0 @@
import { client, navigateTo } from "../index.js";
import AbstractView from "./AbstractView.js";
function game_found(game_id)
{
navigateTo(`/games/${game_id}`)
}
export default class extends AbstractView {
constructor(params) {
super(params, "Dashboard");
}
async postInit()
{
await client.matchmaking.start(game_found)
console.log("start matchmaking")
}
async getHtml() {
return `
<h1>finding<h1>
`;
}
async leavePage()
{
await client.matchmaking.stop();
}
}

View File

@ -0,0 +1,208 @@
import AbstractView from "./AbstractView.js";
import {client} from "../index.js";
import {Message} from "../api/chat/message.js"
export default class extends AbstractView {
constructor(params) {
super(params, "Search");
}
async postInit() {
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("input_user").value.toLowerCase();
let logged = await client.isAuthentificate();
let users = await client.profiles.all();
let list_users = document.getElementById('list_users');
list_users.innerHTML = "";
users.filter(user => user.username.toLowerCase().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);
// space
new_user.appendChild(document.createTextNode(" "));
// chat
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();
});
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
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 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 `
<link rel="stylesheet" href="/static/css/search.css">
<div id="chats">
<div id="users">
<input id="input_user" type="text" name="message" placeholder="userbozo"/>
<ul id="list_users">
</ul>
</div>
</div>
`;
}
}

View File

@ -52,4 +52,4 @@ export default class extends AbstractNonAuthentifiedView {
</div> </div>
`; `;
} }
} }

View File

@ -26,4 +26,4 @@ export default class extends AbstractView {
<a id="username"></a> <a id="username"></a>
`; `;
} }
} }

View File

@ -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 `
<link rel="stylesheet" href="/static/css/profiles/profiles.css">
<div id="profile-list">
</div>
`;
}
}

View File

@ -10,10 +10,10 @@
<body> <body>
<nav class="nav"> <nav class="nav">
<a href="/" class="nav__link" data-link>Dashboard</a> <a href="/" class="nav__link" data-link>Dashboard</a>
<a href="/profiles" class="nav__link" data-link>Profiles</a> <a href="/search" class="nav__link" data-link>Search</a>
<a href="/home" class="nav__link" data-link>Home</a>
<a href="/login" class="nav__link" data-link>Login</a> <a href="/login" class="nav__link" data-link>Login</a>
<a href="/register" class="nav__link" data-link>Register</a> <a href="/register" class="nav__link" data-link>Register</a>
<a href="/chat" class="nav__link" data-link>Chat</a>
</nav> </nav>
<div id="app"></div> <div id="app"></div>
<script type="module" src="{% static 'js/index.js' %}"></script> <script type="module" src="{% static 'js/index.js' %}"></script>

View File

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class GamesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'games'

View File

@ -1,14 +0,0 @@
from django.db import models
# Create your models here.
class GameModel(models.Model):
def create(self, users_id: [int]):
self.save()
for user_id in users_id:
GameMembersModel(game_id=self.pk, member_id=user_id)
return self.pk
class GameMembersModel(models.Model):
game_id = models.IntegerField()
member_id = models.IntegerField()

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class MatchmakingConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'matchmaking'

View File

@ -1,48 +0,0 @@
from channels.generic.websocket import WebsocketConsumer
from django.contrib.auth.models import User
from games.models import GameModel
import json
queue_id: [int] = []
queue_ws: [WebsocketConsumer] = []
class MatchMaking(WebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.channel_name = "matchmaking"
self.group_name = "matchmaking"
def connect(self):
user: User = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
self.channel_layer.group_add(self.group_name, self.channel_name)
self.accept()
global queue_id, queue_ws
queue_id.append(user.pk)
queue_ws.append(self)
if len(set(queue_id)) == 2:
game_id: int = GameModel().create(set(queue_id))
event = {"game_id": game_id}
for ws in queue_ws:
ws.send(text_data=json.dumps({'game_id': game_id}))
queue_id.clear()
queue_ws.clear()
def disconnect(self, close_code):
user: User = self.scope["user"]
global queue_id, queue_ws
if (user.pk in queue_id):
queue_ws.pop(queue_id.index(user.pk))
queue_id.remove(user.pk)
self.channel_layer.group_discard(self.group_name, self.channel_name)

View File

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@ -1,6 +0,0 @@
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/matchmaking/', consumers.MatchMaking.as_asgi())
]

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "ft_transcendence",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@ -10,20 +10,17 @@ https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
import os import os
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack from channels.auth import AuthMiddlewareStack
import chat.routing import chat.routing
import matchmaking.routing
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings')
application = ProtocolTypeRouter({ application = ProtocolTypeRouter({
'http':get_asgi_application(), 'http':get_asgi_application(),
'websocket':AuthMiddlewareStack( 'websocket':AuthMiddlewareStack(
URLRouter( URLRouter(
chat.routing.websocket_urlpatterns + chat.routing.websocket_urlpatterns
matchmaking.routing.websocket_urlpatterns
) )
) )
}) })

View File

@ -43,11 +43,10 @@ INSTALLED_APPS = [
'channels', 'channels',
'daphne', 'daphne',
'matchmaking.apps.MatchmakingConfig',
'games.apps.GamesConfig',
'accounts.apps.AccountsConfig', 'accounts.apps.AccountsConfig',
'profiles.apps.ProfilesConfig', 'profiles.apps.ProfilesConfig',
'frontend.apps.FrontendConfig', 'frontend.apps.FrontendConfig',
'chat.apps.ChatConfig',
'corsheaders', 'corsheaders',
'rest_framework', 'rest_framework',
@ -150,4 +149,4 @@ STATIC_URL = 'static/'
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@ -21,5 +21,6 @@ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('api/profiles/', include('profiles.urls')), path('api/profiles/', include('profiles.urls')),
path('api/accounts/', include('accounts.urls')), path('api/accounts/', include('accounts.urls')),
path('api/chat/', include('chat.urls')),
path('', include('frontend.urls')), path('', include('frontend.urls')),
] ]