Merge pull request 'update malouze cooking' (#6) from main into malouze-cooking

Reviewed-on: https://codeberg.org/adrien-lsh/ft_transcendence/pulls/6
This commit is contained in:
kbz_8
2024-02-14 17:53:57 +00:00
91 changed files with 2665 additions and 1360 deletions

3
.gitignore vendored
View File

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

View File

@ -1,4 +1,24 @@
# BACKEND # FT_TRANSCENDENCE
# Modules
| Module | Nb point | state|
| ------ | -------- | ---- |
| Multiplayer | 2 | 🚧
| Remote | 2 | 🚧
| Bootstrap | 1 | 🏁 |
| Django | 2 | 🏁 |
| Bdd | 1 | 🚧 |
| Accounts | 2 | 🏁 |
| WebGL | 2 | 🚧 |
| Other game | 2 | 🚧 |
| Chat | 2 | 🏁 |
| Translation | 1 | 🚧 |
| Other browser | 1 | 🏁 |
| Smartphone support | 1 | 🚧 |
| --- | --- | ---
| Ready | 8 | |
| Total | 19 | |
## Installation ## Installation
@ -37,6 +57,10 @@ python manage.py makemigrations chat
python manage.py makemigrations tournament python manage.py makemigrations tournament
python manage.py migrate python manage.py migrate
``` ```
- Compile translations
```
python manage.py compilemessages
```
- 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

View File

@ -0,0 +1,22 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-01 13:59+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: views/login.py:22
msgid "Invalid username or password."
msgstr "Nom d'utilisateur ou mot de passe incorect."

View File

@ -4,9 +4,9 @@ from django.core.exceptions import ValidationError
class LoginSerializer(Serializer): class LoginSerializer(Serializer):
username = CharField() username = CharField()
password = CharField() password = CharField()
def get_user(self, data): def get_user(self, data):
user = authenticate(username=data['username'], password=data['password']) user = authenticate(username=data['username'], password=data['password'])
return user return user

View File

@ -6,16 +6,16 @@ from django.http import HttpRequest
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
class DeleteView(APIView): class DeleteView(APIView):
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def delete(self, request: HttpRequest): def delete(self, request: HttpRequest):
data: dict = request.data data: dict = request.data
password: str = data["password"] password: str = data["password"]
if (password is None): if (password is None):
return Response({"password": ["This field may not be blank."]}) return Response({"password": ["This field may not be blank."]})
if (request.user.check_password(password) == False): if (request.user.check_password(password) == False):
return Response({"password": ["Password wrong."]}) return Response({"password": ["Password wrong."]})
request.user.delete() request.user.delete()
logout(request) logout(request)
return Response("user deleted", status=status.HTTP_200_OK) return Response("user deleted", status=status.HTTP_200_OK)

View File

@ -9,8 +9,8 @@ from ..serializers.login import LoginSerializer
class LoggedView(APIView): class LoggedView(APIView):
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
return Response(status = (status.HTTP_200_OK if request.user.is_authenticated else status.HTTP_400_BAD_REQUEST)) return Response(status = (status.HTTP_200_OK if request.user.is_authenticated else status.HTTP_400_BAD_REQUEST))

View File

@ -4,20 +4,21 @@ from rest_framework import permissions, status
from django.http import HttpRequest from django.http import HttpRequest
from django.contrib.auth import login from django.contrib.auth import login
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
from django.utils.translation import gettext as _
from ..serializers.login import LoginSerializer from ..serializers.login import LoginSerializer
class LoginView(APIView): class LoginView(APIView):
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def post(self, request: HttpRequest): def post(self, request: HttpRequest):
data = request.data data = request.data
serializer = LoginSerializer(data=data) serializer = LoginSerializer(data=data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
user = serializer.get_user(data) user = serializer.get_user(data)
if user is None: if user is None:
return Response({'login': ['Invalid username or password.']}, status.HTTP_400_BAD_REQUEST) return Response({'login': [_('Invalid username or password.')]}, status.HTTP_401_UNAUTHORIZED)
login(request, user) login(request, user)
return Response({'id': user.pk}, status=status.HTTP_200_OK) return Response({'id': user.pk}, status=status.HTTP_200_OK)

View File

@ -6,8 +6,8 @@ from django.http import HttpRequest
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
class LogoutView(APIView): class LogoutView(APIView):
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
logout(request) logout(request)
return Response("user unlogged", status=status.HTTP_200_OK) return Response("user unlogged", status=status.HTTP_200_OK)

View File

@ -6,13 +6,13 @@ from django.http import HttpRequest
from django.contrib.auth import login from django.contrib.auth import login
class RegisterView(APIView): class RegisterView(APIView):
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
def post(self, request: HttpRequest): def post(self, request: HttpRequest):
data = request.data data = request.data
serializer = RegisterSerialiser(data=data) serializer = RegisterSerialiser(data=data)
if serializer.is_valid(raise_exception=True): if serializer.is_valid(raise_exception=True):
user = serializer.create(data) user = serializer.create(data)
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

@ -8,244 +8,244 @@ import json
class ChatConsumer(WebsocketConsumer): class ChatConsumer(WebsocketConsumer):
def connect(self): def connect(self):
user = self.scope["user"] user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated): if (user.is_anonymous or not user.is_authenticated):
return return
channel_id : int = int(self.scope['url_route']['kwargs']['chat_id']) channel_id : int = int(self.scope['url_route']['kwargs']['chat_id'])
if ChatMemberModel.objects.filter(member_id=user.pk, channel_id=int(channel_id)).count() != 1: if ChatMemberModel.objects.filter(member_id=user.pk, channel_id=int(channel_id)).count() != 1:
return return
if (self.channel_layer == None): if (self.channel_layer == None):
return return
self.room_group_name = f'chat{channel_id}' self.room_group_name = f'chat{channel_id}'
async_to_sync(self.channel_layer.group_add)( async_to_sync(self.channel_layer.group_add)(
self.room_group_name, self.room_group_name,
self.channel_name self.channel_name
) )
self.accept() self.accept()
def receive(self, text_data=None, bytes_data=None): def receive(self, text_data=None, bytes_data=None):
if text_data == None: if text_data == None:
return return
user = self.scope["user"] user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated): if (user.is_anonymous or not user.is_authenticated):
return return
text_data_json: dict = json.loads(text_data) text_data_json: dict = json.loads(text_data)
message = text_data_json.get('message') message = text_data_json.get('message')
if (message is None): if (message is None):
return return
receivers_id = text_data_json.get('receivers_id') receivers_id = text_data_json.get('receivers_id')
if (receivers_id is None): if (receivers_id is None):
return return
channel_id : int = int(self.scope['url_route']['kwargs']['chat_id']) channel_id : int = int(self.scope['url_route']['kwargs']['chat_id'])
if ChatMemberModel.objects.filter(member_id = user.pk, channel_id = channel_id).count() != 1: if ChatMemberModel.objects.filter(member_id = user.pk, channel_id = channel_id).count() != 1:
return return
if (self.channel_layer == None): if (self.channel_layer == None):
return return
message_time: int = int(time.time() * 1000) message_time: int = int(time.time() * 1000)
if (len(receivers_id) == 1 and if (len(receivers_id) == 1 and
BlockModel.objects.filter(blocker=user.pk, blocked=receivers_id[0]) or BlockModel.objects.filter(blocker=user.pk, blocked=receivers_id[0]) or
BlockModel.objects.filter(blocker=receivers_id[0], blocked=user.pk) BlockModel.objects.filter(blocker=receivers_id[0], blocked=user.pk)
): ):
return return
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',
'author_id':user.pk, 'author_id':user.pk,
'content':message, 'content':message,
'time':message_time, 'time':message_time,
} }
) )
new_message = ChatMessageModel( new_message = ChatMessageModel(
channel_id = channel_id, channel_id = channel_id,
author_id = user.pk, author_id = user.pk,
content = message, content = message,
time = message_time time = message_time
).save() ).save()
def chat_message(self, event): def chat_message(self, event):
user = self.scope["user"] user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated): if (user.is_anonymous or not user.is_authenticated):
return return
channel_id: int = int(self.scope['url_route']['kwargs']['chat_id']) channel_id: int = int(self.scope['url_route']['kwargs']['chat_id'])
if ChatMemberModel.objects.filter(member_id=user.pk, channel_id=channel_id).count() != 1: if ChatMemberModel.objects.filter(member_id=user.pk, channel_id=channel_id).count() != 1:
return return
self.send(text_data=json.dumps({ self.send(text_data=json.dumps({
'type':'chat', 'type':'chat',
'author_id':event['author_id'], 'author_id':event['author_id'],
'content':event['content'], 'content':event['content'],
'time': event['time'], 'time': event['time'],
})) }))
class ChatNoticeConsumer(WebsocketConsumer): class ChatNoticeConsumer(WebsocketConsumer):
def connect(self): def connect(self):
user = self.scope["user"] user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated): #if (user.is_anonymous or not user.is_authenticated):
#return #return
if (self.channel_layer == None): if (self.channel_layer == None):
return return
self.room_group_name = f'chatNotice{user.pk}' self.room_group_name = f'chatNotice{user.pk}'
if (not hasattr(self.channel_layer, "users_channels")): if (not hasattr(self.channel_layer, "users_channels")):
self.channel_layer.users_channels = {} self.channel_layer.users_channels = {}
self.channel_layer.users_channels[user.pk] = self.channel_name self.channel_layer.users_channels[user.pk] = self.channel_name
if (not hasattr(self.channel_layer, "invite")): if (not hasattr(self.channel_layer, "invite")):
self.channel_layer.invite = {} self.channel_layer.invite = {}
self.channel_layer.invite[user.pk] = []; self.channel_layer.invite[user.pk] = [];
async_to_sync(self.channel_layer.group_add)( async_to_sync(self.channel_layer.group_add)(
self.room_group_name, self.room_group_name,
self.channel_name self.channel_name
) )
self.accept() self.accept()
message_time: int = int(time.time() * 1000) message_time: int = int(time.time() * 1000)
targets = list(self.channel_layer.users_channels.keys()) targets = list(self.channel_layer.users_channels.keys())
for target in targets: for target in targets:
channel = self.channel_layer.users_channels.get(target) channel = self.channel_layer.users_channels.get(target)
if (channel == None or target == user.pk): if (channel == None or target == user.pk):
continue continue
async_to_sync(self.channel_layer.send)(channel, { async_to_sync(self.channel_layer.send)(channel, {
'type':"online_users", 'type':"online_users",
'author_id':user.pk, 'author_id':user.pk,
'time':message_time, 'time':message_time,
}) })
def disconnect(self, code): def disconnect(self, code):
user = self.scope["user"] user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated): if (user.is_anonymous or not user.is_authenticated):
return return
self.channel_layer.users_channels.pop(user.pk) self.channel_layer.users_channels.pop(user.pk)
message_time: int = int(time.time() * 1000) message_time: int = int(time.time() * 1000)
targets = list(self.channel_layer.users_channels.keys()) targets = list(self.channel_layer.users_channels.keys())
for target in targets: for target in targets:
channel = self.channel_layer.users_channels.get(target) channel = self.channel_layer.users_channels.get(target)
if (channel == None or target == user.pk): if (channel == None or target == user.pk):
continue continue
async_to_sync(self.channel_layer.send)(channel, { async_to_sync(self.channel_layer.send)(channel, {
'type':"online_users", 'type':"online_users",
'author_id':user.pk, 'author_id':user.pk,
'time':message_time, 'time':message_time,
}) })
def receive(self, text_data=None, bytes_data=None): def receive(self, text_data=None, bytes_data=None):
if text_data == None: if text_data == None:
return return
user = self.scope["user"] user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated): #if (user.is_anonymous or not user.is_authenticated):
#return #return
text_data_json = json.loads(text_data) text_data_json = json.loads(text_data)
type_notice = text_data_json.get('type') type_notice = text_data_json.get('type')
targets : list = text_data_json.get('targets') targets : list = text_data_json.get('targets')
content : dict = text_data_json.get('content') content : dict = text_data_json.get('content')
if (type_notice == None or targets == None): if (type_notice == None or targets == None):
return return
if (self.channel_layer == None): if (self.channel_layer == None):
return return
message_time: int = int(time.time() * 1000) message_time: int = int(time.time() * 1000)
status = 200; status = 200;
#print("receive" + str(user.pk)) #print("receive" + str(user.pk))
if targets == "all": if targets == "all":
targets = list(self.channel_layer.users_channels.keys()) targets = list(self.channel_layer.users_channels.keys())
for target in targets: for target in targets:
channel = self.channel_layer.users_channels.get(target) channel = self.channel_layer.users_channels.get(target)
if (channel == None or target == user.pk): if (channel == None or target == user.pk):
if (channel == None): if (channel == None):
status = 404 status = 404
continue continue
async_to_sync(self.channel_layer.send)(channel, { async_to_sync(self.channel_layer.send)(channel, {
'type':type_notice, 'type':type_notice,
'author_id':user.pk, 'author_id':user.pk,
'content':content, 'content':content,
'time':message_time, 'time':message_time,
'status': 200, 'status': 200,
}) })
async_to_sync(self.channel_layer.send)(self.channel_layer.users_channels.get(user.pk), { async_to_sync(self.channel_layer.send)(self.channel_layer.users_channels.get(user.pk), {
'type':type_notice, 'type':type_notice,
'author_id':user.pk, 'author_id':user.pk,
'content':"notice return", 'content':"notice return",
'time':message_time, 'time':message_time,
'status':status, 'status':status,
}) })
def invite(self, event): def invite(self, event):
user = self.scope["user"] user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated): if (user.is_anonymous or not user.is_authenticated):
return return
if (self.channel_layer.invite[event["author_id"]].get(user.pk)): if (self.channel_layer.invite[event["author_id"]].get(user.pk)):
return return
self.channel_layer.invite[event["author_id"]].append(user.pl) self.channel_layer.invite[event["author_id"]].append(user.pl)
self.send(text_data=json.dumps({ self.send(text_data=json.dumps({
'type':event['type'], 'type':event['type'],
'author_id':event['author_id'], 'author_id':event['author_id'],
'content':event['content'], 'content':event['content'],
'time': event['time'], 'time': event['time'],
'status':event['status'], 'status':event['status'],
})) }))
def online_users(self, event): def online_users(self, event):
user = self.scope["user"] user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated): #if (user.is_anonymous or not user.is_authenticated):
#return #return
#print("online_users" + str(user.pk)) #print("online_users" + str(user.pk))
event['content'] = self.channel_layer.users_channels event['content'] = self.channel_layer.users_channels
self.send(text_data=json.dumps({ self.send(text_data=json.dumps({
'type':event['type'], 'type':event['type'],
'author_id':event['author_id'], 'author_id':event['author_id'],
'content':event['content'], 'content':event['content'],
'time': event['time'], 'time': event['time'],
'status':event['status'], 'status':event['status'],
})) }))

View File

@ -1,177 +1,379 @@
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 games.models import GameModel
from profiles.models import FriendModel, AskFriendModel
import time import time
import json import json
class ChatNoticeConsumer(WebsocketConsumer): class ChatNoticeConsumer(WebsocketConsumer):
def connect(self): def connect(self):
user = self.scope["user"] user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated): #if (user.is_anonymous or not user.is_authenticated):
#return #return
if (self.channel_layer == None): if (self.channel_layer == None):
return return
self.room_group_name = f'chatNotice{user.pk}' self.room_group_name = f'chatNotice{user.pk}'
if (not hasattr(self.channel_layer, "users_channels")): if (not hasattr(self.channel_layer, "users_channels")):
self.channel_layer.users_channels = {} self.channel_layer.users_channels = {}
self.channel_layer.users_channels[user.pk] = self.channel_name self.channel_layer.users_channels[user.pk] = self.channel_name
if (not hasattr(self.channel_layer, "invite")): if (not hasattr(self.channel_layer, "invite")):
self.channel_layer.invite = {} self.channel_layer.invite = {}
self.channel_layer.invite[user.pk] = []; self.channel_layer.invite[user.pk] = [];
async_to_sync(self.channel_layer.group_add)( async_to_sync(self.channel_layer.group_add)(
self.room_group_name, self.room_group_name,
self.channel_name self.channel_name
) )
self.accept() self.accept()
message_time: int = int(time.time() * 1000) self.sync()
targets = list(self.channel_layer.users_channels.keys())
for target in targets:
channel = self.channel_layer.users_channels.get(target)
if (channel == None or target == user.pk):
continue
async_to_sync(self.channel_layer.send)(channel, {
'type':"online_users",
'author_id':user.pk,
'targets': targets,
'time':message_time,
'status': 200,
})
def disconnect(self, code): def disconnect(self, code):
user = self.scope["user"] user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated): if (user.is_anonymous or not user.is_authenticated):
return return
self.channel_layer.users_channels.pop(user.pk) del self.channel_layer.users_channels[user.pk]
del self.channel_layer.invite[user.pk]
message_time: int = int(time.time() * 1000) for inviter_id, inviteds_id in self.channel_layer.invite.items():
if (user.pk in inviteds_id):
self.channel_layer.invite[inviter_id].remove(user.pk)
targets = list(self.channel_layer.users_channels.keys()) self.sync()
for target in targets:
channel = self.channel_layer.users_channels.get(target)
if (channel == None or target == user.pk):
continue
async_to_sync(self.channel_layer.send)(channel, {
'type':"online_users",
'author_id':user.pk,
'targets': targets,
'time':message_time,
'status': 200,
})
def receive(self, text_data=None, bytes_data=None):
if text_data == None:
return
user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated):
#return
text_data_json = json.loads(text_data)
type_notice = text_data_json.get('type')
targets : list = text_data_json.get('targets')
content : dict = text_data_json.get('content')
if (type_notice == None or targets == None):
return
if (self.channel_layer == None):
return
message_time: int = int(time.time() * 1000)
status = 200;
#print("receive" + str(user.pk))
try:
status = getattr(self, "pre_" + type_notice)(user, targets)
except AttributeError:
print(f"La fonction pre_{type_notice} n'existe pas.")
if targets == "all":
targets = list(self.channel_layer.users_channels.keys())
for target in targets:
channel = self.channel_layer.users_channels.get(target)
if (channel == None or target == user.pk):
if (channel == None):
status = 404
continue
async_to_sync(self.channel_layer.send)(channel, {
'type':type_notice,
'author_id':user.pk,
'content':content,
'targets': targets,
'time':message_time,
'status': 200,
})
async_to_sync(self.channel_layer.send)(self.channel_layer.users_channels.get(user.pk), {
'type':type_notice,
'author_id':user.pk,
'content':"notice return",
'targets': targets,
'time':message_time,
'status':status,
})
def pre_invite(self, user, targets): def receive(self, text_data=None, bytes_data=None):
status = 200
for target in targets: if text_data == None:
if (target in self.channel_layer.invite[user.pk]): return
status = 409
return status
def invite(self, event): user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated):
#return
user = self.scope["user"] text_data_json = json.loads(text_data)
if (user.is_anonymous or not user.is_authenticated):
return type_notice = text_data_json.get('type')
targets : list = text_data_json.get('targets')
content : dict = text_data_json.get('content')
if (user.pk in self.channel_layer.invite[event["author_id"]]): if (type_notice == None or targets == None):
return return
if (user.pk != event["author_id"]): if (self.channel_layer == None):
self.channel_layer.invite[event["author_id"]].append(user.pk) return
self.send(text_data=json.dumps({ message_time: int = text_data_json.get('time')
'type':event['type'],
'author_id':event['author_id'],
'content':event['content'],
'targets': event['targets'],
'time': event['time'],
'status':event['status'],
}))
def pre_online_users(self, user, targets): if (message_time == None):
pass message_time: int = int(time.time() * 1000)
def online_users(self, event): result = None
try:
status, result = getattr(self, "pre_" + type_notice)(user, targets)
except AttributeError as error:
status = 200
user = self.scope["user"] if (status < 300):
#if (user.is_anonymous or not user.is_authenticated): if targets == "all":
#return targets = list(self.channel_layer.users_channels.keys())
#print("online_users" + str(user.pk)) for target in targets:
channel = self.channel_layer.users_channels.get(target)
if (channel == None or target == user.pk):
if (channel == None):
status = 444 # target not connected
continue
async_to_sync(self.channel_layer.send)(channel, {
'type':type_notice,
'author_id':user.pk,
'content':content,
'result':result,
'targets': targets,
'time':message_time,
'status': 200,
})
event['content'] = self.channel_layer.users_channels async_to_sync(self.channel_layer.send)(self.channel_layer.users_channels.get(user.pk), {
'type':type_notice,
'author_id':user.pk,
'result':result,
'targets': targets,
'time':message_time,
'status':status,
})
self.send(text_data=json.dumps({ def sync(self, user = None, level = None):
'type':event['type'],
'author_id':event['author_id'], sendToUser = True
'content':event['content'], if (user == None):
'time': event['time'], user = self.scope["user"]
'status':event['status'], sendToUser = False
}))
if (level == None):
level = 0
message_time: int = int(time.time() * 1000)
if (sendToUser):
targets = [user.pk]
else:
targets = list(self.channel_layer.users_channels.keys())
for target in targets:
channel = self.channel_layer.users_channels.get(target)
if (channel == None or (not sendToUser and target == user.pk)):
continue
async_to_sync(self.channel_layer.send)(channel, {
'type':"online_users",
'author_id':user.pk,
'targets': targets,
'time':message_time,
'status': 200,
})
if (level >= 1):
async_to_sync(self.channel_layer.send)(channel, {
'type':"invite",
'author_id':user.pk,
'targets': targets,
'time':message_time,
'status': 200,
})
def pre_invite(self, user, targets):
if (user.is_anonymous or not user.is_authenticated):
return 400, None
status = 200
for target in targets:
if (target in self.channel_layer.invite[user.pk]):
status = 409
continue
channel = self.channel_layer.users_channels.get(target)
if (channel == None):
status = 404
continue
# Add the invited in "self.channel_layer.invite"
if (user.pk != target):
self.channel_layer.invite[user.pk].append(target)
return status, None
def get_invites(self, user):
invites = []
for inviter_id, inviteds_id in self.channel_layer.invite.items():
if (user.pk in inviteds_id and user.pk != inviter_id):
invites.append(inviter_id)
return invites;
def invite(self, event):
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
invites = self.get_invites(user)
self.send(text_data=json.dumps({
'type':event['type'],
'author_id':event['author_id'],
'invites': invites,
'targets': event['targets'],
'time': event['time'],
'status':event['status'],
}))
def pre_accept_invite(self, user, targets):
if (user.is_anonymous or not user.is_authenticated):
return 400, None
if (user.pk not in self.channel_layer.invite[targets[0]]):
return 400, None
self.channel_layer.invite[targets[0]].remove(user.pk)
if (targets[0] in self.channel_layer.invite[user.pk]):
self.channel_layer.invite[user.pk].remove(targets[0])
id_game = GameModel().create([user.pk, targets[0]]);
return 200, id_game
def accept_invite(self, event):
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
invites = self.get_invites(user)
self.send(text_data=json.dumps({
'type':event['type'],
'author_id':event['author_id'],
'id_game': event['result'],
'invites': invites,
'time': event['time'],
'status':event['status'],
}))
def pre_refuse_invite(self, user, targets):
if (user.is_anonymous or not user.is_authenticated):
return 400, None
if (user.pk not in self.channel_layer.invite[targets[0]]):
return 400, None
self.channel_layer.invite[targets[0]].remove(user.pk)
if (targets[0] in self.channel_layer.invite[user.pk]):
self.channel_layer.invite[user.pk].remove(targets[0])
return 200, None
def refuse_invite(self, event):
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
invites = self.get_invites(user)
self.send(text_data=json.dumps({
'type':event['type'],
'author_id':event['author_id'],
'invites': invites,
'time': event['time'],
'status':event['status'],
}))
def pre_ask_friend(self, user, targets):
if (user.is_anonymous or not user.is_authenticated):
return 400, None
if (AskFriendModel.objects.filter(asker=user.pk, asked=targets[0])):
return 409, None
if (FriendModel().isFriend(user.pk, targets[0])):
return 409, None
if (targets[0] != None):
AskFriendModel(asker=user.pk, asked=targets[0]).save()
return 200, None
def send_ask_friend(self, event):
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
asked = AskFriendModel().getAsked(user.pk)
asker = AskFriendModel().getAsker(user.pk)
online_friends = self.get_online_friend(user)
self.send(text_data=json.dumps({
'type':event['type'],
'author_id':event['author_id'],
'targets':event['targets'],
'asker': asker,
'asked': asked,
'online': online_friends,
'time': event['time'],
'status':event['status'],
}))
def ask_friend(self, event):
self.send_ask_friend(event)
def delete_ask(self, asker, user_asked):
if (user_asked.is_anonymous or not user_asked.is_authenticated):
return 400, None
asked = user_asked.pk
if (not AskFriendModel.objects.filter(asker=asker, asked=asked)):
return 404, None
if (FriendModel().isFriend(asker, asked)):
return 409, None
if (not AskFriendModel().deleteAsk(asker, asked)):
return 400, None
return 200, None
def pre_accept_friend(self, user, targets):
status, result = self.delete_ask(targets[0], user)
if (status == 200):
FriendModel(user_id1=user.pk, user_id2=targets[0]).save()
return status, result
def accept_friend(self, event):
self.send_ask_friend(event)
def pre_refuse_friend(self, user, targets):
return self.delete_ask(targets[0], user)
def refuse_friend(self, event):
self.send_ask_friend(event)
def pre_remove_friend(self, user, targets):
if (user.is_anonymous or not user.is_authenticated):
return 400, None
if (not FriendModel().isFriend(user.pk, targets[0])):
return 409, None
if (not FriendModel().deleteFriend(user.pk, targets[0])):
return 400, None
return 200, None
def remove_friend(self, event):
self.send_ask_friend(event)
def get_online_friend(self, user):
online_friends = {}
for friend in FriendModel().getFriends(user.pk):
if (friend in self.channel_layer.users_channels):
online_friends[friend] = "green"
else:
online_friends[friend] = "red"
return online_friends
def online_users(self, event):
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
online_friends = self.get_online_friend(user)
self.send(text_data=json.dumps({
'type':event['type'],
'author_id':event['author_id'],
'online':online_friends,
'time': event['time'],
'status':event['status'],
}))

View File

@ -5,28 +5,28 @@ from django.contrib import admin
# Create your models here. # Create your models here.
class ChatChannelModel(models.Model): class ChatChannelModel(models.Model):
def create(self, users_id: [int]): def create(self, users_id: [int]):
self.save() self.save()
for user_id in users_id: for user_id in users_id:
ChatMemberModel(channel_id = self.pk, member_id = user_id).save() ChatMemberModel(channel_id = self.pk, member_id = user_id).save()
return self.pk return self.pk
def get_members_id(self): def get_members_id(self):
return [member_channel.member_id for member_channel in ChatMemberModel.objects.filter(channel_id = self.pk)] return [member_channel.member_id for member_channel in ChatMemberModel.objects.filter(channel_id = self.pk)]
class ChatMemberModel(models.Model): class ChatMemberModel(models.Model):
member_id = IntegerField(primary_key=False) member_id = IntegerField(primary_key=False)
channel_id = IntegerField(primary_key=False) channel_id = IntegerField(primary_key=False)
def __str__(self): def __str__(self):
return "member_id: " + str(self.member_id) + ", channel_id: " + str(self.channel_id) return "member_id: " + str(self.member_id) + ", channel_id: " + str(self.channel_id)
class ChatMessageModel(models.Model): class ChatMessageModel(models.Model):
channel_id = IntegerField(primary_key=False) channel_id = IntegerField(primary_key=False)
author_id = IntegerField(primary_key=False) author_id = IntegerField(primary_key=False)
content = models.CharField(max_length=255) content = models.CharField(max_length=255)
time = IntegerField(primary_key=False) time = IntegerField(primary_key=False)
def __str__(self): def __str__(self):
return "author_id: " + str(self.author_id) + ", channel_id: " + str(self.channel_id) + ", content: " + self.content return "author_id: " + str(self.author_id) + ", channel_id: " + str(self.channel_id) + ", content: " + self.content

View File

@ -4,6 +4,6 @@ from . import consumersChat
from . import consumersNotice from . import consumersNotice
websocket_urlpatterns = [ websocket_urlpatterns = [
re_path(r'ws/chat/(?P<chat_id>\d+)$', consumersChat.ChatConsumer.as_asgi()), re_path(r'ws/chat/(?P<chat_id>\d+)$', consumersChat.ChatConsumer.as_asgi()),
re_path(r'ws/chat/notice$', consumersNotice.ChatNoticeConsumer.as_asgi()), re_path(r'ws/chat/notice$', consumersNotice.ChatNoticeConsumer.as_asgi()),
] ]

View File

@ -6,25 +6,25 @@ from django.contrib.auth.models import User
# Create your tests here. # Create your tests here.
class ChatTest(TestCase): class ChatTest(TestCase):
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
self.username='bozo1' self.username='bozo1'
self.password='password' self.password='password'
self.user: User = User.objects.create_user(username=self.username, password=self.password) self.user: User = User.objects.create_user(username=self.username, password=self.password)
self.dest: User = User.objects.create_user(username="bozo2", password=self.password) self.dest: User = User.objects.create_user(username="bozo2", password=self.password)
self.url = "/api/chat/" self.url = "/api/chat/"
def test_create_chat(self): def test_create_chat(self):
self.client.login(username=self.username, password=self.password) self.client.login(username=self.username, password=self.password)
response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]}) response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]})
response_dict: dict = eval(response.content) response_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {}) self.assertDictEqual(response_dict, {})
def test_create_chat_unlogged(self): def test_create_chat_unlogged(self):
response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]}) response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]})
response_dict: dict = eval(response.content) response_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {'detail': 'Authentication credentials were not provided.'}) self.assertDictEqual(response_dict, {'detail': 'Authentication credentials were not provided.'})

View File

@ -14,35 +14,35 @@ from .serializers import ChatChannelSerializer, ChatMessageSerializer
class ChannelView(APIView): class ChannelView(APIView):
queryset = ChatChannelModel.objects queryset = ChatChannelModel.objects
serializer_class = ChatChannelSerializer serializer_class = ChatChannelSerializer
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def post(self, request): def post(self, request):
serializer = self.serializer_class(data = request.data) serializer = self.serializer_class(data = request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
data: dict = serializer.validated_data data: dict = serializer.validated_data
members_id = data.get("members_id") members_id = data.get("members_id")
if members_id == None: if members_id == None:
return Response({"detail": "members_id is None."}, status = status.HTTP_400_BAD_REQUEST) return Response({"detail": "members_id is None."}, status = status.HTTP_400_BAD_REQUEST)
if self.request.user.pk not in members_id: if self.request.user.pk not in members_id:
return Response({"detail": "You are trying to create a chat group without you."}, status = status.HTTP_400_BAD_REQUEST) return Response({"detail": "You are trying to create a chat group without you."}, status = status.HTTP_400_BAD_REQUEST)
for member_channel in ChatMemberModel.objects.filter(member_id = members_id[0]): for member_channel in ChatMemberModel.objects.filter(member_id = members_id[0]):
channel_id: int = member_channel.channel_id channel_id: int = member_channel.channel_id
if not ChatChannelModel.objects.filter(pk = channel_id).exists(): if not ChatChannelModel.objects.filter(pk = channel_id).exists():
continue continue
channel: ChatChannelModel = ChatChannelModel.objects.get(pk = channel_id) channel: ChatChannelModel = ChatChannelModel.objects.get(pk = channel_id)
if set(channel.get_members_id()) == set(members_id): if set(channel.get_members_id()) == set(members_id):
messages = ChatMessageModel.objects.filter(channel_id = channel_id).order_by("time") messages = ChatMessageModel.objects.filter(channel_id = channel_id).order_by("time")
messages = serializers.serialize("json", messages) messages = serializers.serialize("json", messages)
return Response({'channel_id': channel_id, 'messages': messages}, status=status.HTTP_200_OK) return Response({'channel_id': channel_id, 'messages': messages}, status=status.HTTP_200_OK)
new_channel_id = ChatChannelModel().create(members_id) new_channel_id = ChatChannelModel().create(members_id)
return Response({'channel_id': new_channel_id}, status=status.HTTP_201_CREATED) return Response({'channel_id': new_channel_id}, status=status.HTTP_201_CREATED)

View File

@ -1,12 +1,3 @@
*{
color: #cccccc;
font-size: 35px;
background-color: #1a1a1a;
}
body {
}
#app #avatar { #app #avatar {
max-height: 10em; max-height: 10em;
max-width: 10em; max-width: 10em;
@ -34,3 +25,7 @@ body {
opacity: 0; opacity: 0;
transition: opacity 0.25s; transition: opacity 0.25s;
} }
#languageSelector > .dropdown-item.active {
background-color: transparent;
}

View File

@ -1,17 +0,0 @@
#app #main .account
{
color: #1a1a1a;
}
#app #main
{
width: 60%;
display: flex;
flex-direction: column;
color: #1a1a1a;
}
#app #main .profile
{
color: #1a1a1a;
}

View File

@ -1,14 +1,25 @@
#app * {
font-size: 30px;
}
#app #username #app #username
{ {
font-size: 0.8em; font-size: 0.8em;
} }
#app #block { #app #block, #app #friend {
cursor: pointer; cursor: pointer;
font-size: 0.7em; font-size: 0.7em;
text-decoration: underline; text-decoration: underline;
} }
#app { #app {
margin-top: 20px; margin-top: 1em;
}
#app #yes, #app #no {
display:inline;
cursor: pointer;
font-size: 0.7em;
text-decoration: underline;
} }

View File

@ -1,3 +1,7 @@
#app * {
font-size: 40px;
}
#app img #app img
{ {
@ -78,7 +82,6 @@
border: none; border: none;
outline: none; outline: none;
border-bottom: 0.15em solid green; border-bottom: 0.15em solid green;
caret-color: green;
color: green; color: green;
font-size: 0.8em; font-size: 0.8em;
} }
@ -106,9 +109,8 @@
word-wrap: break-word; word-wrap: break-word;
} }
#app #invite { #app #invite, #app #yes, #app #no {
position: relative; position: relative;
background-color: green;
border: none; border: none;
color: white; color: white;
text-align: center; text-align: center;
@ -118,3 +120,15 @@
width: 4em; width: 4em;
cursor: pointer; cursor: pointer;
} }
#app #yes, #app #no {
position: relative;
border: none;
color: white;
text-align: center;
text-decoration: none;
font-size: 0.8em;
height: 2em;
width: 2em;
cursor: pointer;
}

View File

@ -0,0 +1,11 @@
#app * {
font-size: 30px;
}
#app #main
{
width: 60%;
display: flex;
flex-direction: column;
}

View File

@ -1,45 +1,74 @@
import { reloadView } from '../index.js'
export default class LanguageManager { export default class LanguageManager {
constructor() { constructor() {
this.availableLanguages = ['en', 'fr']; this.availableLanguages = ['en', 'fr', 'tp', 'cr'];
this.dict = null;
this.currentLang = 'en' this.currentLang = 'en'
this.chosenLang = localStorage.getItem('preferedLanguage') || this.currentLang; this.chosenLang = localStorage.getItem('preferedLanguage') || this.currentLang;
if (this.chosenLang !== this.currentLang && this.availableLanguages.includes(this.chosenLang)) { if (this.chosenLang !== this.currentLang && this.availableLanguages.includes(this.chosenLang)) {
this.translatePage(); this.loading = this.translatePage();
this.currentLang = this.chosenLang;
} else {
this.loading = this.loadDict(this.chosenLang);
} }
document.getElementById('languageDisplay').innerHTML =
document.querySelector(`#languageSelector > [value=${this.currentLang}]`)?.innerHTML;
} }
async translatePage() { async translatePage() {
if (this.currentLang === this.chosenLang) if (this.currentLang === this.chosenLang)
return; return;
let dictUrl = `${location.origin}/static/js/lang/${this.chosenLang}.json`; await this.loadDict(this.chosenLang);
let translation = await fetch(dictUrl).then(response => { if (!this.dict)
if (response.status !== 200)
return null;
return response.json();
});
if (!translation) {
console.log(`No translation found for language ${this.chosenLang}`);
return 1; return 1;
}
document.querySelectorAll('[data-i18n]').forEach(el => { document.querySelectorAll('[data-i18n]').forEach(el => {
let key = el.getAttribute('data-i18n'); let key = el.getAttribute('data-i18n');
el.innerHTML = translation[key]; el.innerHTML = this.dict[key];
}) })
await reloadView();
this.currentLang = this.chosenLang;
return 0; return 0;
} }
async changeLanguage(lang) { async changeLanguage(lang) {
if (lang === this.currentLang || !this.availableLanguages.includes(lang)) if (lang === this.currentLang || !this.availableLanguages.includes(lang))
return; return 1;
this.chosenLang = lang; this.chosenLang = lang;
if (await this.translatePage() !== 0) if (await this.translatePage() !== 0)
return; return 1;
this.currentLang = this.chosenLang;
localStorage.setItem('preferedLanguage', lang); localStorage.setItem('preferedLanguage', lang);
document.getElementById('languageDisplay').innerHTML =
document.querySelector(`#languageSelector > [value=${this.currentLang}]`)?.innerHTML;
return 0;
}
async loadDict(lang) {
let dictUrl = `${location.origin}/static/js/lang/${lang}.json`;
let response = await fetch(dictUrl);
if (response.status !== 200) {
console.log(`No translation found for language ${lang}`);
return;
}
this.dict = await response.json();
}
async waitLoading() {
await this.loading;
}
get(key, defaultTxt) {
if (!this.dict)
return defaultTxt;
return this.dict[key] || defaultTxt;
} }
} }

View File

@ -9,7 +9,7 @@ class MyProfile extends Profile
*/ */
constructor (client) constructor (client)
{ {
super(client, "me") super(client, "../me")
} }
/** /**
@ -19,7 +19,7 @@ class MyProfile extends Profile
*/ */
async change_avatar(form_data) async change_avatar(form_data)
{ {
let response = await this.client._patch_file(`/api/profiles/me`, form_data); let response = await this.client._patch_file(`/api/profiles/settings`, form_data);
let response_data = await response.json() let response_data = await response.json()
return response_data; return response_data;
@ -27,4 +27,4 @@ class MyProfile extends Profile
} }
export {MyProfile} export {MyProfile}

View File

@ -16,19 +16,16 @@ class Account
/** /**
* @param {String} username * @param {String} username
* @param {String} password * @param {String} password
* @returns {?Promise<Object>} * @returns {Response}
*/ */
async create(username, password) async create(username, password)
{ {
let response = await this.client._post("/api/accounts/register", {username: username, password: password}); let response = await this.client._post("/api/accounts/register", {username: username, password: password});
let response_data = await response.json()
if (response_data == "user created") if (response.status === 201)
{
await this.client._update_logged(true); await this.client._update_logged(true);
return null;
} return response;
return response_data
} }
/** /**

View File

@ -1,3 +1,4 @@
import { navigateTo } from "../../index.js";
import {create_popup} from "../../utils/noticeUtils.js"; import {create_popup} from "../../utils/noticeUtils.js";
class Notice { class Notice {
@ -5,11 +6,10 @@ class Notice {
this.client = client; this.client = client;
this.data = {}; this.data = {};
// users online, invited by, asked friend by, // users online, invited by ..., asked by ..., asker to ...
let data_variable = ["online", "invited", "asked"]; let data_variable = ["online", "invited", "asked", "asker"];
for (let i in data_variable) { for (let i in data_variable)
this.data[data_variable[i]] = []; this.data[data_variable[i]] = [];
}
this.connect(); this.connect();
@ -20,15 +20,20 @@ class Notice {
this.chatSocket = new WebSocket(url); this.chatSocket = new WebSocket(url);
this.chatSocket.onmessage = (event) =>{ this.chatSocket.onmessage = (event) =>{
let data = JSON.parse(event.data); let send = JSON.parse(event.data);
//console.log("notice: ", data); //console.log("notice: ", send);
if (data.type == "invite")
this.receiveInvite(data); try {
else if (data.type == "online_users" || data.type == "disconnect") this["receive_" + send.type](send);
this.receiveOnlineUser(data); }
catch (error) {
console.log("receive_" + send.type + ": Function not found");
}
} }
this.chatSocket.onopen = (event) => { this.chatSocket.onopen = (event) => {
this.getOnlineUser(); this.getOnlineUser();
this.ask_friend();
} }
} }
@ -37,64 +42,126 @@ class Notice {
return ; return ;
this.chatSocket.close(); this.chatSocket.close();
}
async reconnect() {
this.disconnect();
this.connect();
}
async accept_invite(invitedBy) {
this.sendRequest({
type: "accept_invite",
targets: [invitedBy],
});
} }
async sendInvite(id_inviter, id_inviteds) { async receive_accept_invite(send) {
if (this.chatSocket == undefined)
return;
this.chatSocket.send(JSON.stringify({ this.data["invited"] = send.invites;
let id_game = send["id_game"];
navigateTo("/game/" + id_game);
}
async refuse_invite(invitedBy) {
this.sendRequest({
type: "refuse_invite",
targets: [invitedBy],
});
}
async receive_refuse_invite(send) {
this.data["invited"] = send.invites;
if (send.author_id == this.client.me.id) {
if (this.rewrite_invite !== undefined)
this.rewrite_invite();
}
else {
let sender = await this.client.profiles.getProfileId(send.author_id);
create_popup(sender.username + " refuse your invitation");
}
}
async send_invite(id_inviteds) {
this.sendRequest({
type: "invite", type: "invite",
targets: id_inviteds, targets: id_inviteds,
})); time: new Date().getTime(),
});
} }
async receiveInvite(data) { async receive_invite(send) {
if (data.content === "notice return") { if (this.client.me == undefined)
if (data.status == 200) { return ;
for (let target in data.targets)
this.data["invited"].push(target); let content = send.invites;
if (send.author_id == this.client.me.id) {
if (send.status == 200) {
for (let target in send.targets)
return create_popup("Invitation send"); return create_popup("Invitation send");
} }
else if (data.status == 404) else if (send.status == 444)
return create_popup("User not connected"); return create_popup("User not connected");
else if (data.status == 409) else if (send.status == 409)
return create_popup("Already invited"); return create_popup("Already invited");
} }
else { else {
let sender = await this.client.profiles.getProfile(data.author_id);
this.inviter.push(data.author_id); // Regarder qu'il est bien invité par l'auteur
// Et qu'il n'est pas déjà invité
if (!content.includes(send.author_id) ||
this.data["invited"].includes(send.author_id))
return;
this.data["invited"] = content;
let sender = await this.client.profiles.getProfileId(send.author_id);
create_popup("Invitation received by " + sender.username); create_popup("Invitation received by " + sender.username);
// Géré la reception de l'invitation if (this.rewrite_invite !== undefined)
this.rewrite_invite();
} }
} }
async getOnlineUser() { async getOnlineUser() {
if (this.chatSocket == undefined)
return;
this.online_users = {}; this.online_users = {};
this.chatSocket.send(JSON.stringify({ this.sendRequest({
type: "online_users", type: "online_users",
targets: "all", targets: [],
})); time: new Date().getTime(),
});
} }
async receiveOnlineUser(data) { async receive_online_users(send) {
if (data.content !== undefined) { let content = send.online;
if (content !== undefined) {
if (this.online_users.length > 0) { if (this.data["online"].length > 0) {
// get all disconnect user // get all disconnect user
let disconnects = this.online_users.filter(id => !Object.keys(data.content).includes(id)); //let disconnects = this.data["online"].filter(id => !Object.keys(content).includes(id));
let disconnects = [];
for (const [key, value] of Object.entries(this.data["online"])) {
if (content[key] == "red" && value == "green")
disconnects.push(key);
}
// delete invite // delete invite
this.data["invited"] = this.data["invited"].filter(id => !disconnects.includes(id)); this.data["invited"] = this.data["invited"].filter(id => !disconnects.includes(id));
@ -102,12 +169,139 @@ class Notice {
//console.log(this.data["invited"]); //console.log(this.data["invited"]);
} }
this.data["online"] = Object.keys(data.content); this.data["online"] = content;
if (this.rewrite_usernames !== undefined) if (this.rewrite_usernames !== undefined)
this.rewrite_usernames(); this.rewrite_usernames();
} }
} }
async ask_friend(user_id=undefined) {
this.sendRequest({
type: "ask_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_ask_friend(send) {
let my_id = (this.client.me && this.client.me.id) || send.author_id;
if (send.author_id == my_id) {
if (send.status == 400)
create_popup("Friend ask error");
else if (send.status == 409)
create_popup("Already asked friend");
}
//if (!send.asked.includes(send.author_id) ||
//this.data["asked"].includes(send.author_id))
//return;
//if (!send.asker.includes(send.author_id) ||
//this.data["asker"].includes(send.author_id))
//return;
this.data["asked"] = send.asked;
this.data["asker"] = send.asker;
if (send.author_id != my_id) {
let sender = await this.client.profiles.getProfileId(send.author_id);
if (this.data["asker"].includes(send.author_id))
create_popup(sender.username + " ask you as friend");
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
}
}
async remove_friend(user_id) {
this.sendRequest({
type: "remove_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_remove_friend(send) {
if (send.author_id == this.client.me.id) {
if (send.status == 400)
create_popup("Error remove Friend");
else if (send.status == 409)
create_popup("Not friend, wtf");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
this.receive_online_users(send);
}
async accept_friend(user_id) {
this.sendRequest({
type: "accept_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_accept_friend(send) {
this.data["asked"] = send.asked;
this.data["asker"] = send.asker;
let sender = await this.client.profiles.getProfileId(send.author_id);
if (send.author_id == this.client.me.id) {
if (send.status == 400)
create_popup("Error accept Friend");
else if (send.status == 404)
create_popup("Not found request Friend");
else if (send.status == 409)
create_popup("Already Friend, wtf");
}
else {
create_popup(sender.username + " accept your friend request");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
this.receive_online_users(send);
}
async refuse_friend(user_id) {
this.sendRequest({
type: "refuse_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_refuse_friend(send) {
this.data["asked"] = send.asked;
this.data["asker"] = send.asker;
let sender = await this.client.profiles.getProfileId(send.author_id);
if (send.author_id == this.client.me.id) {
if (send.status == 400)
create_popup("Error refuse Friend");
else if (send.status == 404)
create_popup("Not found request Friend");
else if (send.status == 409)
create_popup("Already Friend, WTF");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
}
async sendRequest(content) {
if (this.chatSocket == undefined)
return;
this.chatSocket.send(JSON.stringify(content));
}
} }
export {Notice} export {Notice}

View File

@ -53,7 +53,7 @@ class Client
this.tournaments = new Tourmanents(this); this.tournaments = new Tourmanents(this);
/** /**
* @type {Boolean} A private var represent if the is is log NEVER USE IT use await isAuthentificated() * @type {Boolean} A private var represent if the is is log NEVER USE IT use await isAuthenticated()
*/ */
this._logged = undefined; this._logged = undefined;
@ -73,13 +73,14 @@ class Client
this.notice = new Notice(this); this.notice = new Notice(this);
this.lang = new LanguageManager; this.lang = new LanguageManager;
} }
/** /**
* The only right way to determine is the user is logged * The only right way to determine is the user is logged
* @returns {Promise<Boolean>} * @returns {Promise<Boolean>}
*/ */
async isAuthentificate() async isAuthenticated()
{ {
if (this._logged == undefined) if (this._logged == undefined)
this._logged = await this._test_logged(); this._logged = await this._test_logged();
@ -114,7 +115,8 @@ class Client
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-CSRFToken": getCookie("csrftoken"), "X-CSRFToken": getCookie("csrftoken"),
}, 'Accept-Language': this.lang.currentLang,
},
body: JSON.stringify(data), body: JSON.stringify(data),
}); });
return response; return response;
@ -190,6 +192,7 @@ class Client
{ {
this.me = new MyProfile(this); this.me = new MyProfile(this);
await this.me.init(); await this.me.init();
this.notice.reconnect();
document.getElementById('navbarLoggedOut').classList.add('d-none'); document.getElementById('navbarLoggedOut').classList.add('d-none');
document.getElementById('navbarLoggedIn').classList.remove('d-none'); document.getElementById('navbarLoggedIn').classList.remove('d-none');
document.getElementById('navbarDropdownButton').innerHTML = this.me.username; document.getElementById('navbarDropdownButton').innerHTML = this.me.username;
@ -198,6 +201,7 @@ class Client
else else
{ {
this.me = undefined; this.me = undefined;
this.notice.reconnect();
document.getElementById('navbarLoggedOut').classList.remove('d-none'); document.getElementById('navbarLoggedOut').classList.remove('d-none');
document.getElementById('navbarLoggedIn').classList.add('d-none'); document.getElementById('navbarLoggedIn').classList.add('d-none');
document.getElementById('navbarDropdownButton').innerHTML = 'Me'; document.getElementById('navbarDropdownButton').innerHTML = 'Me';
@ -232,7 +236,7 @@ class Client
} }
/** /**
* Determine if the user is logged. NEVER USE IT, USE isAuthentificated() * Determine if the user is logged. NEVER USE IT, USE isAuthenticated()
* @returns {Promise<Boolean>} * @returns {Promise<Boolean>}
*/ */
async _test_logged() async _test_logged()

View File

@ -4,6 +4,7 @@ import { GameConfig } from "./GameConfig.js"
import { Player } from "./Player.js"; import { Player } from "./Player.js";
import { Time } from "./Time.js"; import { Time } from "./Time.js";
import { Wall } from "./Wall.js"; import { Wall } from "./Wall.js";
import { Client } from "../client.js";
class Game class Game
{ {
@ -21,7 +22,7 @@ class Game
/** /**
* *
* @returns {Number} * @returns {Promise<Number>}
*/ */
async init() async init()
{ {
@ -32,23 +33,52 @@ class Game
let response_data = await response.json(); let response_data = await response.json();
/**
* @type {[Number]}
*/
this.players_id = response_data.players_id; this.players_id = response_data.players_id;
/**
* @type {String}
*/
this.state = response_data.state; this.state = response_data.state;
/**
* @type {Boolean}
*/
this.started = response_data.started; this.started = response_data.started;
/**
* @type {Boolean}
*/
this.finished = response_data.finished; this.finished = response_data.finished;
/**
* @type {Number}
*/
this.winner_id = this.finished ? response_data.winner_id : undefined; this.winner_id = this.finished ? response_data.winner_id : undefined;
if (this.finished === true) if (this.finished === true)
return 0; return 0;
/**
* @type {GameConfig}
*/
this.config = new GameConfig(this.client); this.config = new GameConfig(this.client);
let ret = await this.config.init(); let ret = await this.config.init();
if (ret !== 0) if (ret !== 0)
return ret; return ret;
/**
* @type {Time}
*/
this.time = new Time(); this.time = new Time();
this.last_pos = null
/**
* @type {Boolean}
*/
this._inited = false; this._inited = false;
return 0; return 0;
@ -72,14 +102,14 @@ class Game
* *
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
*/ */
draw(ctx) render(ctx)
{ {
if(ctx instanceof CanvasRenderingContext2D) if(ctx instanceof CanvasRenderingContext2D)
{ {
ctx.clearRect(0, 0, this.config.size_x, this.config.size_y); ctx.clearRect(0, 0, this.config.size_x, this.config.size_y);
} }
this.draw_sides(ctx); this.draw_sides(ctx);
this.ball.draw(ctx); this.ball.render(ctx);
} }
_send(data) _send(data)
@ -93,50 +123,25 @@ class Game
} }
} }
/**
* @param {Number} position
* @param {Number} time
*/
_send_paddle_position(position, time) _send_paddle_position(position, time)
{ {
if (this.last_pos !== null && this.last_pos.time >= time) this._send({"detail": "update_my_paddle_pos", ...{"time": time, "position": position}});
return;
this.last_pos = {"time": time, "position": position};
this._send({"detail": "update_my_paddle_pos", ...this.last_pos});
}
_receive_player_join(player_data)
{
console.log(player_data)
let index = this.players.indexOf((player) => player.id === player_data.user_id);
this.players[index].is_connected = true;
}
_receive_player_leave(player_data)
{
let index = this.players.indexOf((player) => player.id === player_data.user_id);
this.players[index].is_connected = false;
}
_receive_update_ball(data)
{
this.ball.position_x = data.position_x
this.ball.position_y = data.position_y
this.ball.position_x = data.position_x
this.ball.position_x = data.position_x
} }
_receive_update_paddle(data) _receive_update_paddle(data)
{ {
let player = this.players.find((player) => player.id === data.user_id); let player = this.players.find((player) => player.id === data.user_id);
if (player === null) player.from_json(data);
{ }
this._receive_player_join(data);
return; _receive_ball(data)
} {
player.is_connected = data.is_connected; this.ball.from_json(data);
player.update_pos(data.position.position, data.position.time);
} }
_receive(data) _receive(data)
@ -144,26 +149,30 @@ class Game
if (data.detail === "update_paddle") if (data.detail === "update_paddle")
this._receive_update_paddle(data); this._receive_update_paddle(data);
else if (data.detail === "update_ball") else if (data.detail === "update_ball")
this._receive_update_ball(data); this._receive_ball(data)
else if (data.detail === "init_game") else if (data.detail === "init_game")
this._init_game(data) this._init_game(data);
else if (data.detail === "player_join")
this._receive_player_join(data)
else if (data.detail === "player_leave")
this._receive_player_leave(data)
} }
_init_game(data) _init_game(data)
{ {
const ball_data = data.ball; /**
this.ball = new Ball(this, ball_data.position_x, ball_data.position_y, ball_data.velocity_x, ball_data.velocity_y); * @type {Ball}
*/
this.ball = (new Ball(this)).from_json(data.ball)
/**
* @type {[Wall]}
*/
this.walls = []; this.walls = [];
const walls_data = data.walls; const walls_data = data.walls;
walls_data.forEach((wall_data) => { walls_data.forEach((wall_data) => {
this.walls.push(new Wall().from_json(wall_data)); this.walls.push(new Wall().from_json(wall_data));
}); });
/**
* @type {[Player]}
*/
this.players = [] this.players = []
const players_data = data.players; const players_data = data.players;
players_data.forEach((player_data) => { players_data.forEach((player_data) => {

View File

@ -24,14 +24,17 @@ class GameConfig
* @type {Number} * @type {Number}
*/ */
this.size_x = response_data.MAP_SIZE_X; this.size_x = response_data.MAP_SIZE_X;
/** /**
* @type {Number} * @type {Number}
*/ */
this.size_y = response_data.MAP_SIZE_Y; this.size_y = response_data.MAP_SIZE_Y;
/** /**
* @type {Number} * @type {Number}
*/ */
this.center_x = this.size_x / 2; this.center_x = this.size_x / 2;
/** /**
* @type {Number} * @type {Number}
*/ */
@ -63,10 +66,12 @@ class GameConfig
* @type {Number} * @type {Number}
*/ */
this.ball_size = response_data.BALL_SIZE; this.ball_size = response_data.BALL_SIZE;
/** /**
* @type {Number} * @type {Number}
*/ */
this.ball_spawn_x = this.center_x; this.ball_spawn_x = this.center_x;
/** /**
* @type {Number} * @type {Number}
*/ */

View File

@ -9,8 +9,32 @@ class Segment
*/ */
constructor(start, stop) constructor(start, stop)
{ {
/**
* @type {Point}
*/
this.start = start === undefined ? new Point() : start; this.start = start === undefined ? new Point() : start;
/**
* @type {Point}
*/
this.stop = stop === undefined ? new Point() : stop; this.stop = stop === undefined ? new Point() : stop;
}
angle()
{
let x = this.start.x - this.stop.x,
y = this.start.y - this.stop.y;
return Math.atan2(y, x);
}
len()
{
let x = this.start.x - this.stop.x,
y = this.start.y - this.stop.y;
return (x ** 2 + y ** 2) ** (1 / 2);
} }
/** /**

View File

@ -17,7 +17,9 @@ class Time
deltaTime() deltaTime()
{ {
return (this._current_frame - this._last_frame) !== NaN ? this._current_frame - this._last_frame : 0; if (this._last_frame === undefined)
return 0;
return (this._current_frame - this._last_frame);
} }
deltaTimeSecond() deltaTimeSecond()

View File

@ -23,7 +23,7 @@ class MatchMaking
*/ */
async start(receive_func, disconnect_func, mode) async start(receive_func, disconnect_func, mode)
{ {
if (!await this.client.isAuthentificate()) if (!await this.client.isAuthenticated())
return null; return null;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/matchmaking/${mode}`; let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/matchmaking/${mode}`;

View File

@ -5,7 +5,7 @@ class Profile
/** /**
* @param {Client} client * @param {Client} client
*/ */
constructor (client, username, id = undefined, avatar_url = undefined) constructor (client, username=undefined, id=undefined, avatar_url=undefined)
{ {
/** /**
* @type {Client} client * @type {Client} client
@ -31,6 +31,7 @@ class Profile
* @type {Boolean} * @type {Boolean}
*/ */
this.isBlocked = false; this.isBlocked = false;
this.isFriend = false;
} }
/** /**
@ -39,7 +40,11 @@ class Profile
*/ */
async init() async init()
{ {
let response = await this.client._get(`/api/profiles/${this.username}`); let response;
if (this.username !== undefined)
response = await this.client._get(`/api/profiles/user/${this.username}`);
else
response = await this.client._get(`/api/profiles/id/${this.id}`);
if (response.status !== 200) if (response.status !== 200)
return response.status; return response.status;
@ -47,25 +52,49 @@ class Profile
let response_data = await response.json(); let response_data = await response.json();
this.id = response_data.user_id; this.id = response_data.user_id;
this.username = response_data.username; this.username = response_data.username;
this.avatar_url = response_data.avatar_url; this.avatar_url = response_data.avatar;
if (this.client.me == undefined) await this.getBlock();
return; await this.getFriend();
}
async getBlock() {
let block_response = await this.client._get("/api/profiles/block"); let block_response = await this.client._get("/api/profiles/block");
if (block_response.status != 200) if (block_response.status != 200)
return return
let block_data = await block_response.json(); let block_data = await block_response.json();
let block_list = JSON.parse(block_data); let block_list = JSON.parse(block_data["blockeds"]);
let client_id = block_data["user_id"];
block_list.forEach(block => { block_list.forEach(block => {
let blocker = block.fields.blocker; let blocker = block.fields.blocker;
let blocked = block.fields.blocked; let blocked = block.fields.blocked;
if (blocker == this.client.me.user_id && blocked == user_id) if (blocker == client_id && blocked == this.id)
return this.isBlocked = true; return this.isBlocked = true;
}); });
} }
async getFriend() {
let friend_response = await this.client._get("/api/profiles/friend");
this.isFriend = false;
if (friend_response.status != 200)
return this.isFriend;
let friend_data = await friend_response.json();
let friends_list = friend_data["friends"];
let client_id = friend_data["user_id"];
friends_list.forEach(friend => {
if (friend == this.id) {
this.isFriend = true;
return this.isFriend;
}
});
return this.isFriend;
}
} }
export {Profile} export {Profile}

View File

@ -24,7 +24,7 @@ class Profiles
let profiles = [] let profiles = []
response_data.forEach((profile) => { response_data.forEach((profile) => {
profiles.push(new Profile(this.client, profile.username, profile.user_id, profile.avatar_url)) profiles.push(new Profile(this.client, profile.username, profile.user_id, profile.avatar))
}); });
return profiles; return profiles;
} }
@ -32,7 +32,7 @@ class Profiles
/** /**
* *
* @param {String} username * @param {String} username
* @returns {?Profile} * @returns {?Promise<Profile>}
*/ */
async getProfile(username) async getProfile(username)
{ {
@ -42,6 +42,14 @@ class Profiles
return profile; return profile;
} }
async getProfileId(id)
{
let profile = new Profile(this.client, undefined, id);
if (await profile.init())
return null;
return profile;
}
/** /**
* Block a user * Block a user
* @param {Number} user_id * @param {Number} user_id
@ -51,7 +59,7 @@ class Profiles
// blocker & blocked // blocker & blocked
let response = await this.client._post("/api/profiles/block", { let response = await this.client._post("/api/profiles/block", {
users_id:[this.client.me.user_id, user_id], users_id:[this.client.me.id, user_id],
}); });
let data = await response.json(); let data = await response.json();
@ -68,7 +76,7 @@ class Profiles
// blocker & blocked // blocker & blocked
let response = await this.client._delete("/api/profiles/block", { let response = await this.client._delete("/api/profiles/block", {
users_id:[this.client.me.user_id, user_id], users_id:[this.client.me.id, user_id],
}); });
let data = await response.json(); let data = await response.json();

View File

@ -120,7 +120,7 @@ class Tourmanent
*/ */
async join(receive_func, disconnect_func) async join(receive_func, disconnect_func)
{ {
if (!await this.client.isAuthentificate()) if (!await this.client.isAuthenticated())
return null; return null;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/tournaments/${this.id}`; let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/tournaments/${this.id}`;

View File

@ -1,10 +1,8 @@
import { Client } from "./api/client.js"; import { Client } from "./api/client.js";
import LoginView from "./views/accounts/LoginView.js";
import Dashboard from "./views/Dashboard.js"; import Dashboard from "./views/Dashboard.js";
import Search from "./views/Search.js"; import Search from "./views/Search.js";
import HomeView from "./views/HomeView.js"; import HomeView from "./views/HomeView.js";
import RegisterView from "./views/accounts/RegisterView.js";
import LogoutView from "./views/accounts/LogoutView.js"; import LogoutView from "./views/accounts/LogoutView.js";
import GameOfflineView from "./views/GameOfflineView.js"; import GameOfflineView from "./views/GameOfflineView.js";
//import GameView from "./views/GameView.js"; //import GameView from "./views/GameView.js";
@ -13,17 +11,19 @@ import GameView from "./views/GameView3D.js";
import PageNotFoundView from './views/PageNotFoundView.js' import PageNotFoundView from './views/PageNotFoundView.js'
import AbstractRedirectView from "./views/abstracts/AbstractRedirectView.js"; import AbstractRedirectView from "./views/abstracts/AbstractRedirectView.js";
import MeView from "./views/MeView.js"; import SettingsView from "./views/SettingsView.js";
import ProfilePageView from "./views/ProfilePageView.js"; import ProfilePageView from "./views/ProfilePageView.js";
import MatchMakingView from "./views/MatchMakingView.js"; import MatchMakingView from "./views/MatchMakingView.js";
import TournamentPageView from "./views/tournament/TournamentPageView.js"; import TournamentPageView from "./views/tournament/TournamentPageView.js";
import TournamentsView from "./views/tournament/TournamentsListView.js"; import TournamentsView from "./views/tournament/TournamentsListView.js";
import TournamentCreateView from "./views/tournament/TournamentCreateView.js"; import TournamentCreateView from "./views/tournament/TournamentCreateView.js";
import AuthenticationView from "./views/accounts/AuthenticationView.js";
let client = new Client(location.protocol + "//" + location.host) let client = new Client(location.origin);
let lang = client.lang;
let lastView = undefined let lastView = undefined;
let lastPageUrlBeforeLogin = undefined let lastPageUrlBeforeLogin = undefined;
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$"); const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
@ -37,11 +37,12 @@ const getParams = match => {
}; };
const navigateTo = async (uri) => { const navigateTo = async (uri) => {
if (await router(uri) !== 0)
return;
history.pushState(null, null, uri); history.pushState(null, null, uri);
if (await router(uri) !== 0)
return;
let link = document.querySelector('a[href=\'' + location.pathname + '\']'); let link = document.querySelector('a[href=\'' + location.pathname + '\']');
if (link) { if (link) {
document.querySelector('[data-link].active')?.classList.remove('active'); document.querySelector('[data-link].active')?.classList.remove('active');
@ -49,9 +50,14 @@ const navigateTo = async (uri) => {
} }
}; };
const reloadView = async _ => {
await lastView?.leavePage();
await renderView(lastView);
}
async function renderView(view) async function renderView(view)
{ {
let content = await view.getHtml(); let content = await view?.getHtml();
if (content == null) if (content == null)
return 1; return 1;
@ -74,12 +80,12 @@ const router = async(uri) => {
{ path: "/tournaments/create", view: TournamentCreateView }, { path: "/tournaments/create", view: TournamentCreateView },
{ path: "/tournaments/:id", view: TournamentPageView }, { path: "/tournaments/:id", view: TournamentPageView },
{ path: "/tournaments/", view: TournamentsView }, { path: "/tournaments/", view: TournamentsView },
{ path: "/login", view: LoginView }, { path: "/login", view: AuthenticationView },
{ path: "/register", view: AuthenticationView },
{ path: "/logout", view: LogoutView }, { path: "/logout", view: LogoutView },
{ path: "/register", view: RegisterView },
{ path: "/search", view: Search }, { path: "/search", view: Search },
{ path: "/home", view: HomeView }, { path: "/home", view: HomeView },
{ path: "/me", view: MeView }, { path: "/settings", view: SettingsView },
{ path: "/matchmaking", view: MatchMakingView }, { path: "/matchmaking", view: MatchMakingView },
{ path: "/games/offline", view: GameOfflineView }, { path: "/games/offline", view: GameOfflineView },
{ path: "/games/:id", view: GameView }, { path: "/games/:id", view: GameView },
@ -110,12 +116,13 @@ const router = async(uri) => {
const view = new match.route.view(getParams(match), lastPageUrlBeforeLogin); const view = new match.route.view(getParams(match), lastPageUrlBeforeLogin);
if (!(view instanceof AuthenticationView) && ! (view instanceof LogoutView))
lastPageUrlBeforeLogin = uri;
if (view instanceof AbstractRedirectView && await view.redirect()) if (view instanceof AbstractRedirectView && await view.redirect())
return 1; return 1;
lastView = view; lastView = view;
if (uri !== '/login' && uri !== '/register' && uri !== '/logout')
lastPageUrlBeforeLogin = uri;
if (await renderView(view)) if (await renderView(view))
return 1; return 1;
@ -138,13 +145,22 @@ document.addEventListener("DOMContentLoaded", async () => {
}); });
//Languages //Languages
await lang.waitLoading();
Array.from(document.getElementById('languageSelector').children).forEach(el => { Array.from(document.getElementById('languageSelector').children).forEach(el => {
el.onclick = _ => client.lang.changeLanguage(el.value); el.onclick = async _ => {
if (await lang.changeLanguage(el.value))
return;
console.log(lang);
document.querySelector('#languageSelector > .active')?.classList.remove('active');
el.classList.add('active');
};
}); });
document.querySelector(`#languageSelector > [value=${lang.chosenLang}]`)
?.classList.add('active');
await client.isAuthentificate(); await client.isAuthenticated();
router(location.pathname); router(location.pathname);
document.querySelector('a[href=\'' + location.pathname + '\']')?.classList.add('active'); document.querySelector('a[href=\'' + location.pathname + '\']')?.classList.add('active');
}); });
export { client, navigateTo } export { client, lang, navigateTo, reloadView }

View File

@ -0,0 +1,39 @@
{
"navbarSearch": "cherchbeh",
"navbarHome": "Quoicoubouse",
"navbarLogin": "Quoicouconnec",
"navbarRegister": "Quoicougistré",
"navbarProfile": "Mon crampté Profile",
"navbarSettings": "Roue Crampté",
"navbarLogout": "Déconnexion crampté",
"homeWindowTitle": "Quoicoubouse",
"homeTitle": "Quoicoubouse",
"homeOnline": "Jouer en crampté",
"homeOffline": "Jouer hors crampté",
"homeSettings": "Roue Crampté",
"homeLogout": "Déconnexion crampté",
"loginWindowTitle": "Quoicouconnec",
"loginFormTitle": "Quoicouconnec",
"loginFormUsername": "Nom d'crampté",
"loginFormPassword": "Mot de crampté",
"loginFormButton": "Quoicouconnec",
"loginNoAccount": "Pas de compte encore crampté?",
"loginRegister": "Quoicougistré",
"errorEmptyField": "Ce champ ne peut pas être vide crampté.",
"logoutWindowTitle": "Déconnexion crampté",
"registerWindowTitle": "Quoicougistré",
"registerFormTitle": "Quoicougistré",
"registerFormUsername": "Nom d'crampté",
"registerFormPassword": "Mot de crampté",
"registerFormButton": "Quoicougistré",
"registerAlreadyAccount": "Déjà un compte crampté?",
"registerLogin": "Quoicouconnec",
"404WindowTitle": "Pas crampté",
"SearchWindowTitle": "cherchbeh",
"profileAddFriend": "Demander le cramptéman",
"profileRemoveFriend": "Supprimer le cramptéman",
"profileDenyRequest": "Refuser le cramptéman",
"profileAcceptRequest": "Accepter le cramptéman",
"profileUnblock": "Quoicoudebloquer",
"profileBlock": "Quoicoubloquer"
}

View File

@ -5,5 +5,35 @@
"navbarRegister": "Register", "navbarRegister": "Register",
"navbarProfile": "My Profile", "navbarProfile": "My Profile",
"navbarSettings": "Settings", "navbarSettings": "Settings",
"navbarLogout": "Logout" "navbarLogout": "Logout",
"homeWindowTitle": "Home",
"homeTitle": "Home",
"homeOnline": "Play online",
"homeOffline": "Play offline",
"homeSettings": "Settings",
"homeLogout": "Logout",
"loginWindowTitle": "Login",
"loginFormTitle": "Login",
"loginFormUsername": "Username",
"loginFormPassword": "Password",
"loginFormButton": "Login",
"loginNoAccount": "No account yet?",
"loginRegister": "Register",
"errorEmptyField": "This field may not be blank.",
"logoutWindowTitle": "Logout",
"registerWindowTitle": "Register",
"registerFormTitle": "Register",
"registerFormUsername": "Username",
"registerFormPassword": "Password",
"registerFormButton": "Register",
"registerAlreadyAccount": "Already have an account?",
"registerLogin": "Login",
"404WindowTitle": "Not Found",
"SearchWindowTitle": "Search",
"profileAddFriend": "Ask Friend",
"profileRemoveFriend": "Remove Friend",
"profileDenyRequest": "Decline Friend",
"profileAcceptRequest": "Accept Friend",
"profileUnblock": "Unblock",
"profileBlock": "Block"
} }

View File

@ -1,9 +1,39 @@
{ {
"navbarSearch": "Recherche", "navbarSearch": "Recherche",
"navbarHome": "Maison", "navbarHome": "Maison",
"navbarLogin": "Se connecter", "navbarLogin": "Connexion",
"navbarRegister": "S'inscrire", "navbarRegister": "S'inscrire",
"navbarProfile": "Mon Profil", "navbarProfile": "Mon Profil",
"navbarSettings": "Paramètres", "navbarSettings": "Paramètres",
"navbarLogout": "Se déconnecter" "navbarLogout": "Déconnexion",
"homeWindowTitle": "Maison",
"homeTitle": "Maison",
"homeOnline": "Jouer en ligne",
"homeOffline": "Jouer hors ligne",
"homeSettings": "Paramètres",
"homeLogout": "Déconnexion",
"loginWindowTitle": "Connexion",
"loginFormTitle": "Connexion",
"loginFormUsername": "Nom d'utilisateur",
"loginFormPassword": "Mot de passe",
"loginFormButton": "Connexion",
"loginNoAccount": "Pas de compte?",
"loginRegister": "S'inscrire",
"errorEmptyField": "Ce champ ne peut être vide.",
"logoutWindowTitle": "Déconnexion",
"registerWindowTitle": "S'inscrire",
"registerFormTitle": "S'inscrire",
"registerFormUsername": "Nom d'utilisateur",
"registerFormPassword": "Mot de passe",
"registerFormButton": "S'inscrire",
"registerAlreadyAccount": "Déjà un compte?",
"registerLogin": "Connexion",
"404WindowTitle": "Pas trouvé",
"SearchWindowTitle": "Recherche",
"profileAddFriend": "Demander en ami",
"profileRemoveFriend": "Retirer l'ami",
"profileDenyRequest": "Refuser l'ami",
"profileAcceptRequest": "Accepter l'ami",
"profileUnblock": "Débloquer",
"profileBlock": "Bloquer"
} }

View File

@ -0,0 +1,40 @@
{
"navbarSearch": "Lukin",
"navbarHome": "Tomo",
"navbarLogin": "Open",
"navbarRegister": "Sitelen",
"navbarProfile": "Sitelen mi",
"navbarSettings": "Nasin",
"navbarLogout": "Tawa ala",
"homeWindowTitle": "Tomo",
"homeTitle": "Tomo",
"homeOnline": "Mute tawa",
"homeOffline": "Mute lon",
"homeSettings": "Nasin",
"homeLogout": "Tawa ala",
"loginWindowTitle": "Open",
"loginFormTitle": "Open",
"loginFormUsername": "nimi pi jan Open",
"loginFormPassword": "nimi nasa",
"loginFormButton": "Open",
"loginNoAccount": "sina wile ala wile jo e nimi pi jan Open?",
"loginRegister": "Sitelen",
"errorEmptyField": "nimi ni li wile sitelen.",
"logoutWindowTitle": "Tawa ala",
"registerWindowTitle": "Sitelen",
"registerFormTitle": "Sitelen",
"registerFormUsername": "nimi pi jan sin",
"registerFormPassword": "nimi nasa",
"registerFormButton": "Sitelen",
"registerAlreadyAccount": "sina jo ala jo e nimi pi jan sin?",
"registerLogin": "Open",
"404WindowTitle": "Ala o lukin e ni",
"SearchWindowTitle": "Lukin",
"profileAddFriend": "kama jo e jan",
"profileRemoveFriend": "tawa ala e jan",
"profileDenyRequest": "ante e ijo ni",
"profileAcceptRequest": "kama jo e ijo ni",
"profileUnblock": "Tawa ala e nimi pi jan ni",
"profileBlock": "Tawa e nimi pi jan ni"
}

View File

@ -19,6 +19,10 @@ export default class extends AbstractView {
document.getElementById('stopGameButton').onclick = this.stopGame.bind(this); document.getElementById('stopGameButton').onclick = this.stopGame.bind(this);
} }
async leavePage() {
this.game?.cleanup();
}
startGame() { startGame() {
if (this.game == null) { if (this.game == null) {
document.getElementById('startGameButton').innerHTML = 'Reset Game'; document.getElementById('startGameButton').innerHTML = 'Reset Game';
@ -26,6 +30,7 @@ export default class extends AbstractView {
} }
else { else {
document.getElementById('app').removeChild(this.game.canvas); document.getElementById('app').removeChild(this.game.canvas);
document.getElementById('app').removeChild(this.game.scoresDisplay);
this.game.cleanup(); this.game.cleanup();
this.game = new Game; this.game = new Game;
} }

View File

@ -13,7 +13,7 @@ export default class extends AbstractView
this.my_player = undefined; this.my_player = undefined;
} }
keyStretchHandler(event) keyReleaseHandler(event)
{ {
const idx = this.keys_pressed.indexOf(event.key); const idx = this.keys_pressed.indexOf(event.key);
if (idx != -1) if (idx != -1)
@ -26,7 +26,7 @@ export default class extends AbstractView
this.keys_pressed.push(event.key); this.keys_pressed.push(event.key);
} }
draw() render_game()
{ {
const canva = document.getElementById('canva'); const canva = document.getElementById('canva');
@ -40,36 +40,39 @@ export default class extends AbstractView
ctx.beginPath(); ctx.beginPath();
this.game.draw(ctx); this.game.render(ctx);
ctx.strokeStyle = "#000000"; ctx.strokeStyle = "#000000";
ctx.lineWidth = 10; ctx.lineWidth = 1;
ctx.stroke(); ctx.stroke();
} }
render_game() render()
{ {
let loop_id = setInterval(() => { let loop_id = setInterval(() => {
if (this.game === undefined) if (this.game === undefined)
clearInterval(loop_id); clearInterval(loop_id);
if (this.my_player) if (this.my_player)
this.my_player.update_paddle(this.keys_pressed); this.my_player.update_paddle(this.keys_pressed);
this.draw(); this.render_game();
this.game?.time.new_frame(); this.game?.time.new_frame();
//clearInterval(loop_id);
// 1 sec fps // 1 sec fps
}, 1000 / 60); }, 1000 / 60);
} }
register_key() register_key()
{ {
document.addEventListener('keydown', this.keyPressHandler.bind(this)); this.keyPressHandler = this.keyPressHandler.bind(this);
document.addEventListener('keyup', this.keyStretchHandler.bind(this)); this.keyReleaseHandler = this.keyReleaseHandler.bind(this);
document.addEventListener('keydown', this.keyPressHandler);
document.addEventListener('keyup', this.keyReleaseHandler);
} }
unregister_key() unregister_key()
{ {
document.removeEventListener('keydown', this.keyPressHandler); document.removeEventListener('keydown', this.keyPressHandler);
document.removeEventListener('keyup', this.keyStretchHandler); document.removeEventListener('keyup', this.keyReleaseHandler);
} }
async join_game() async join_game()
@ -99,7 +102,7 @@ export default class extends AbstractView
this.register_key() this.register_key()
this.render_game(); this.render();
} }
async update_game_state() async update_game_state()

View File

@ -1,18 +1,19 @@
import AbstractAuthentificateView from "./abstracts/AbstractAuthentifiedView.js"; import { lang } from "../index.js";
import AbstractAuthenticatedView from "./abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthentificateView { export default class extends AbstractAuthenticatedView {
constructor(params) { constructor(params) {
super(params, "Home"); super(params, 'homeWindowTitle');
this.redirect_url = "/login" this.redirect_url = "/login"
} }
async getHtml() { async getHtml() {
return /* HTML */ ` return /* HTML */ `
<h1>HOME</h1> <h1>${lang.get('homeTitle', 'Home')}</h1>
<a href="/matchmaking" class="nav__link" data-link>Play a game</a> <a href="/matchmaking" data-link>${lang.get('homeOnline', 'Play online')}</a>
<a href="/games/offline" class="nav__link" data-link>Play offline</a> <a href="/games/offline" data-link>${lang.get('homeOffline', 'Play offline')}</a>
<a href="/me" class="nav__link" data-link>Me</a> <a href="/settings" data-link>${lang.get('homeSettings', 'Settings')}</a>
<a href="/logout" class="nav__link" data-link>Logout</a> <a href="/logout" data-link>${lang.get('homeLogout', 'Logout')}</a>
`; `;
} }
} }

View File

@ -1,8 +1,8 @@
import { client, navigateTo } from "../index.js"; import { client, navigateTo } from "../index.js";
import { clear, fill_errors } from "../utils/formUtils.js"; import { clear, fill_errors } from "../utils/formUtils.js";
import AbstractAuthentifiedView from "./abstracts/AbstractAuthentifiedView.js"; import AbstractAuthenticatedView from "./abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthentifiedView { export default class extends AbstractAuthenticatedView {
constructor(params) constructor(params)
{ {
super(params, "Matchmaking"); super(params, "Matchmaking");

View File

@ -1,13 +1,15 @@
import AbstractView from "./abstracts/AbstractView.js"; import AbstractView from "./abstracts/AbstractView.js";
import { lang } from '../index.js'
export default class extends AbstractView { export default class extends AbstractView {
constructor(params) { constructor(params) {
super(params, "Dashboard"); super(params, '404WindowTitle');
} }
async getHtml() { async getHtml() {
return ` return `
<h1>404 Bozo</h1> <h1>404 Bozo</h1>
<img src="https://media.giphy.com/media/pm0BKtuBFpdM4/giphy.gif">
<p>Git gud</p> <p>Git gud</p>
`; `;
} }

View File

@ -1,25 +1,26 @@
import AbstractView from "./abstracts/AbstractView.js"; import AbstractView from "./abstracts/AbstractView.js";
import { client } from "../index.js" import { client, lang } from "../index.js"
export default class extends AbstractView { export default class extends AbstractView {
constructor(params) { constructor(params) {
super(params, params.username); super(params, decodeURI(params.username));
this.username = params.username; this.username = decodeURI(params.username);
} }
async postInit() async postInit()
{ {
this.profile = await client.profiles.getProfile(this.username); this.profile = await client.profiles.getProfile(this.username);
if (this.profile === null) if (this.profile === null)
return 404; return 404;
this.userId = this.profile.id; this.user_id = this.profile.id;
this.info = document.getElementById("info"); this.info = document.getElementById("info");
// Username // Username
let username = document.createElement("a"); let username = document.createElement("a");
username.id = "username"; username.id = "username";
username.appendChild(document.createTextNode(this.profile.username)); username.appendChild(document.createTextNode(this.username));
this.info.appendChild(username); this.info.appendChild(username);
this.info.appendChild(document.createElement("br")); this.info.appendChild(document.createElement("br"));
@ -31,30 +32,97 @@ export default class extends AbstractView {
this.info.appendChild(avatar); this.info.appendChild(avatar);
await this.blockButton(); await this.blockButton();
await this.friendButton();
client.notice.rewrite_profile = async () => {
let result = await this.profile.getFriend();
await this.profile.getBlock()
await this.friendButton();
}
} }
async blockButton() { async blockButton() {
// Block option // Block option
if (await client.isAuthentificate() === false) if (await client.isAuthenticated() === false)
return; return;
if (client.me.id != this.userId) { if (client.me.id != this.user_id) {
let block = document.getElementById("block") || document.createElement("a"); let block = document.getElementById("block");
if (block == undefined) {
block = document.createElement("p");
this.info.appendChild(block);
}
block.id = "block"; block.id = "block";
block.innerText = "";
block.onclick = async () => { block.onclick = async () => {
if (!this.profile.isBlocked) if (!this.profile.isBlocked)
await client.profiles.block(this.userId); await client.profiles.block(this.user_id);
else else
await client.profiles.deblock(this.userId); await client.profiles.deblock(this.user_id);
this.profile = await client.profiles.getProfile(this.username); this.profile = await client.profiles.getProfile(this.username);
this.blockButton(); this.blockButton();
}; };
if (this.profile.isBlocked) if (this.profile.isBlocked)
block.appendChild(document.createTextNode("Deblock")); block.textContent = lang.get('profileUnblock', 'Unblock');
else else
block.appendChild(document.createTextNode("Block")); block.textContent = lang.get('profileBlock', 'Block');
this.info.appendChild(block); }
}
async friendButton() {
if (await client.isAuthenticated() === false)
return;
if (client.me.id != this.user_id) {
let yes = document.getElementById("yes") || document.createElement("p");
let no = document.getElementById("no") || document.createElement("p");
let friend = document.getElementById("friend") || document.createElement("p");
if (client.notice.data["asker"].includes(this.user_id)) {
if (friend)
friend.remove();
yes.id = "yes";
yes.textContent = lang.get('profileAcceptRequest', 'Accept Friend');
yes.onclick = async () => {
client.notice.accept_friend(this.user_id);
}
no.id = "no";
no.textContent = lang.get('profileDenyRequest', 'Decline Friend');
no.onclick = async () => {
client.notice.refuse_friend(this.user_id);
}
this.info.appendChild(yes);
this.info.appendChild(document.createTextNode(" "));
this.info.appendChild(no);
}
else {
if (yes && no)
yes.remove(); no.remove();
friend.id = "friend"
friend.onclick = async () => {
if (this.profile.isFriend)
await client.notice.remove_friend(this.user_id);
else
await client.notice.ask_friend(this.user_id);
await client.profiles.getProfile(this.username);
this.friendButton();
};
if (this.profile.isFriend)
friend.textContent = lang.get('profileRemoveFriend', 'Remove Friend');
else {
friend.textContent = lang.get('profileAddFriend', 'Ask Friend');
}
this.info.appendChild(friend);
}
} }
} }

View File

@ -1,27 +1,27 @@
import AbstractView from "./abstracts/AbstractView.js"; import AbstractView from "./abstracts/AbstractView.js";
import {client} from "../index.js"; import { client, lang } from "../index.js";
import {Message} from "../api/chat/message.js" import {Message} from "../api/chat/message.js"
export default class extends AbstractView { export default class extends AbstractView {
constructor(params) { constructor(params) {
super(params, "Search"); super(params, 'SearchWindowTitle');
} }
async wait_get_online_users() { async wait_get_online_users() {
return new Promise((resolve) => { return new Promise((resolve) => {
const checkInterval = setInterval(() => { const checkInterval = setInterval(() => {
//console.log(client.notice.data["online"].length); console.log(client.notice.data["online"]);
if (client.notice.data["online"].length > 0) { if (Object.keys(client.notice.data["online"]).length > 0) {
clearInterval(checkInterval); clearInterval(checkInterval);
resolve(); resolve();
} }
}, 100); }, 1);
}); });
} }
async postInit() { async postInit() {
let logged = await client.isAuthentificate(); let logged = await client.isAuthenticated();
let profiles = await client.profiles.all(); let profiles = await client.profiles.all();
//console.log(client.notice.data); //console.log(client.notice.data);
@ -29,11 +29,13 @@ export default class extends AbstractView {
return console.log("Error"); return console.log("Error");
//await client.notice.getOnlineUser(); //await client.notice.getOnlineUser();
await this.wait_get_online_users(); //await this.wait_get_online_users();
client.notice.rewrite_usernames = this.rewrite_usernames; client.notice.rewrite_usernames = this.rewrite_usernames;
client.notice.rewrite_invite = this.display_invite;
let search = document.getElementById("input_user"); let search = document.getElementById("input_user");
search.oninput = () => this.display_users(logged, profiles); if (search != undefined)
search.oninput = () => this.display_users(logged, profiles);
let chat_input = document.getElementById("input_chat"); let chat_input = document.getElementById("input_chat");
//chat_input.addEventListener("keydown", this.display_chat_manager) //chat_input.addEventListener("keydown", this.display_chat_manager)
@ -66,7 +68,12 @@ export default class extends AbstractView {
username.setAttribute('data-link', ''); username.setAttribute('data-link', '');
username.id = `username${user.id}` username.id = `username${user.id}`
username.href = `/profiles/${user.username}`; username.href = `/profiles/${user.username}`;
username.style.color = client.notice.data["online"].includes(user.id.toString()) ? "green" : "red"; if (logged && user.id == client.me.id)
username.style.color = "green";
else {
let online = client.notice.data["online"][user.id];
username.style.color = online !== undefined ? online : "gray";
}
username.appendChild(document.createTextNode(user.username)); username.appendChild(document.createTextNode(user.username));
new_user.appendChild(username); new_user.appendChild(username);
@ -135,8 +142,14 @@ export default class extends AbstractView {
profiles.filter(user => user.username.toLowerCase().startsWith(search) == true).forEach((user) => { profiles.filter(user => user.username.toLowerCase().startsWith(search) == true).forEach((user) => {
let username = document.getElementById(`username${user.id}`); let username = document.getElementById(`username${user.id}`);
if (username !== null) if (username !== null) {
username.style.color = client.notice.data["online"].includes(user.id.toString()) ? "green" : "red"; if (user.id == client.me.id)
username.style.color = "green";
else {
let online = client.notice.data["online"][user.id];
username.style.color = online !== undefined ? online : "gray";
}
}
}); });
} }
@ -175,13 +188,15 @@ export default class extends AbstractView {
chat_input.maxLength=255; chat_input.maxLength=255;
chat.appendChild(chat_input); chat.appendChild(chat_input);
let members_id = client.channels.channel.members_id;
chat_input.onkeydown = async () => { chat_input.onkeydown = async () => {
if (event.keyCode == 13 && client.channels.channel != undefined) { if (event.keyCode == 13 && client.channels.channel != undefined) {
//let chat_input = document.getElementById("input_chat"); //let chat_input = document.getElementById("input_chat");
let chat_text = chat_input.value; let chat_text = chat_input.value;
let receivers_id = []; let receivers_id = [];
client.channels.channel.members_id.forEach((member_id) => { members_id.forEach((member_id) => {
if (member_id != client.me.id) if (member_id != client.me.id)
receivers_id.push(profiles.filter(user => user.id == member_id)[0].id); receivers_id.push(profiles.filter(user => user.id == member_id)[0].id);
}); });
@ -196,7 +211,7 @@ export default class extends AbstractView {
// Scroll to the bottom of messages // Scroll to the bottom of messages
messages.scrollTop = messages.scrollHeight; messages.scrollTop = messages.scrollHeight;
this.display_invite(chat); this.display_invite();
} }
@ -242,33 +257,86 @@ export default class extends AbstractView {
} }
async display_members(chat, profiles) { async display_members(chat, profiles) {
let members_id = client.channels.channel.members_id;
let members = document.createElement("h2"); let members = document.createElement("h2");
members.id = "members"; members.id = "members";
let usernames = ""; let usernames = "";
client.channels.channel.members_id.forEach((member_id) => { members_id.forEach((member_id) => {
if (member_id != client.me.id) { if (member_id != client.me.id) {
if (usernames.length > 0) if (usernames.length > 0)
usernames += ", "; usernames += ", ";
usernames += (profiles.filter(user => user.id == member_id)[0].username); usernames += (profiles.filter(user => user.id == member_id)[0].username);
} }
}); });
members.appendChild(document.createTextNode(usernames)); members.textContent = usernames;
chat.appendChild(members); chat.appendChild(members);
return members return members
} }
async display_invite(chat, profiles) { async display_invite() {
let chat = document.getElementById("chat");
if (chat == undefined)
return ;
let members_id = client.channels.channel.members_id;
let others = members_id.filter(id => id !== client.me.id);
// Button to send invite to play
let invite = document.getElementById("invite") || document.createElement("button"); let invite = document.getElementById("invite") || document.createElement("button");
invite.id = "invite"; let yes = document.getElementById("yes") || document.createElement("button");
invite.innerText = "invite"; let no = document.getElementById("no") || document.createElement("button");
invite.onclick = async () => {
await client.notice.sendInvite(client.me.id, let invitedBy = undefined;
client.channels.channel.members_id.filter(id => id !== client.me.id)); for (let x in others) {
}; if (client.notice.data["invited"].includes(others[x])) {
chat.appendChild(invite); invitedBy = others[x];
}
}
if (invitedBy == undefined) {
if (yes && no) {
yes.remove();
no.remove();
}
// Button to send invite to play
invite.id = "invite";
invite.style.background = "orange";
invite.innerText = "invite";
invite.title = "Invite to play a game"
invite.onclick = async () => {
await client.notice.send_invite(others);
};
chat.appendChild(invite);
}
else {
if (invite)
invite.remove()
yes.id = "yes";
yes.style.background = "green";
yes.title = "Accept to play a game"
yes.onclick = async () => {
await client.notice.accept_invite(invitedBy);
};
no.id = "no";
no.style.background = "red";
no.title = "Refuse to play a game"
no.onclick = async () => {
await client.notice.refuse_invite(invitedBy);
};
chat.appendChild(yes);
chat.appendChild(no);
}
} }

View File

@ -1,12 +1,13 @@
import { client, navigateTo } from "../index.js"; import { client, navigateTo } from "../index.js";
import { clear, fill_errors } from "../utils/formUtils.js"; import { clear, fill_errors } from "../utils/formUtils.js";
import AbstractAuthentificateView from "./abstracts/AbstractAuthentifiedView.js"; import AbstractAuthenticatedView from "./abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthentificateView export default class extends AbstractAuthenticatedView
{ {
constructor(params) constructor(params)
{ {
super(params, "Me"); super(params, "Settings");
this.PROFILE_PICTURE_MAX_SIZE = 2 * 1024 * 1024; // 2MB
} }
async postInit() async postInit()
@ -24,7 +25,7 @@ export default class extends AbstractAuthentificateView
document.getElementById("avatar").remove(); document.getElementById("avatar").remove();
let avatar = document.createElement("img"); let avatar = document.createElement("img");
avatar.id = "avatar"; avatar.id = "avatar";
avatar.src = profile.avatar_url; avatar.src = profile.avatar_url + '?t=' +new Date().getTime();
document.getElementsByClassName("avatar")[0].appendChild(avatar); document.getElementsByClassName("avatar")[0].appendChild(avatar);
} }
} }
@ -35,7 +36,7 @@ export default class extends AbstractAuthentificateView
let response_data = await client.account.delete(current_password); let response_data = await client.account.delete(current_password);
console.log(await client.isAuthentificate()) console.log(await client.isAuthenticated())
if (response_data === null || response_data === "user deleted") if (response_data === null || response_data === "user deleted")
{ {
navigateTo("/login"); navigateTo("/login");
@ -78,18 +79,24 @@ export default class extends AbstractAuthentificateView
if (avatar.files[0] !== undefined) if (avatar.files[0] !== undefined)
{ {
if (avatar.files[0].size > this.PROFILE_PICTURE_MAX_SIZE) {
document.getElementById("save-profile").classList.add('text-danger');
document.getElementById("save-profile").innerHTML = "Image too large :/";
return;
}
let form_data = new FormData(); let form_data = new FormData();
form_data.append("file", avatar.files[0]); form_data.append("avatar", avatar.files[0]);
await client.me.change_avatar(form_data); await client.me.change_avatar(form_data);
this.display_avatar(); this.display_avatar();
} }
document.getElementById("save-profile").classList.remove('text-danger');
document.getElementById("save-profile").innerHTML = "Saved"; document.getElementById("save-profile").innerHTML = "Saved";
} }
async getHtml() async getHtml()
{ {
return /* HTML */ ` return /* HTML */ `
<link rel="stylesheet" href="/static/css/me.css"> <link rel="stylesheet" href="/static/css/settings.css">
<h1>ME</h1> <h1>ME</h1>
<div id="main"> <div id="main">
<div class="avatar"> <div class="avatar">

View File

@ -2,13 +2,13 @@ import { client, navigateTo } from "../../index.js";
import AbstractRedirectView from "./AbstractRedirectView.js"; import AbstractRedirectView from "./AbstractRedirectView.js";
export default class extends AbstractRedirectView{ export default class extends AbstractRedirectView{
constructor(params, title) { constructor(params, titleKey, uri = "/login") {
super(params, title, "/login"); super(params, titleKey, uri);
} }
async redirect() async redirect()
{ {
if (await client.isAuthentificate() === false) if (await client.isAuthenticated() === false)
{ {
navigateTo(this.redirect_url); navigateTo(this.redirect_url);
return 1; return 1;

View File

@ -2,13 +2,13 @@ import { client, navigateTo } from "../../index.js";
import AbstractRedirectView from "./AbstractRedirectView.js"; import AbstractRedirectView from "./AbstractRedirectView.js";
export default class extends AbstractRedirectView{ export default class extends AbstractRedirectView{
constructor(params, title, url) { constructor(params, titleKey, uri = "/home") {
super(params, title, url); super(params, titleKey, uri);
} }
async redirect() async redirect()
{ {
if (await client.isAuthentificate() === false) if (await client.isAuthenticated() === false)
return 0; return 0;
navigateTo(this.redirect_url); navigateTo(this.redirect_url);
return 1; return 1;

View File

@ -2,14 +2,14 @@ import { navigateTo } from "../../index.js";
import AbstractView from "./AbstractView.js"; import AbstractView from "./AbstractView.js";
export default class extends AbstractView{ export default class extends AbstractView{
constructor(params, title, url) constructor(params, titleKey, uri)
{ {
super(params, title); super(params, titleKey);
this.redirect_url = url; this.redirect_url = uri;
} }
async redirect() async redirect()
{ {
navigateTo(url); navigateTo(this.redirect_url);
} }
} }

View File

@ -1,7 +1,9 @@
import {lang} from '../../index.js'
export default class { export default class {
constructor(params, title) { constructor(params, titleKey) {
this.params = params; this.params = params;
this.title = title; this.titleKey = titleKey;
} }
async postInit() { async postInit() {
@ -11,7 +13,7 @@ export default class {
} }
setTitle() { setTitle() {
document.title = this.title; document.title = lang.get(this.titleKey, 'Bozo Pong');
} }
async getHtml() { async getHtml() {

View File

@ -0,0 +1,177 @@
import { client, lang, navigateTo } from "../../index.js";
import { clear, fill_errors } from "../../utils/formUtils.js";
import AbstractNonAuthenticatedView from "../abstracts/AbstractNonAuthenticatedView.js";
export default class extends AbstractNonAuthenticatedView
{
constructor(params, lastUrlBeforeLogin = '/home')
{
super(params, 'loginWindowTitle', lastUrlBeforeLogin);
this.redirect_url = lastUrlBeforeLogin;
this.current_mode = undefined
}
async leavePage()
{
this.current_mode = undefined;
}
/**
* @returns {Promise}
*/
async postInit()
{
let element = document.getElementById("toggle-register-login");
element.onclick = this.toggle_register_login.bind(this);
let new_mode = location.pathname.slice(1);
this.update_mode(new_mode);
document.getElementById("button").onclick = this.authentication.bind(this);
let username_input = document.getElementById('username-input'),
password_input = document.getElementById('password-input');
[username_input, password_input].forEach(input => {
input.addEventListener('keydown', async ev => {
if (ev.key === 'Enter')
await this.authentication.bind(this)()
});
});
username_input.focus();
}
/**
* Check if field is normal
* @param username {String}
* @param password {String}
* @returns {Boolean}
*/
basic_verif(username, password)
{
if (username === '')
document.getElementById('username').innerHTML = lang.get('errorEmptyField', 'This field may not be blank.');
if (password === '')
document.getElementById('password').innerHTML = lang.get('errorEmptyField', 'This field may not be blank.');
if (username === '' || password === '')
return false;
return true
}
/**
* @returns { undefined }
*/
toggle_register_login(event)
{
event.preventDefault();
let new_mode = this.current_mode === "register" ? "login" : "register";
this.update_mode(new_mode);
}
/**
* @param {String} new_mode
*/
update_mode(new_mode)
{
if (new_mode === this.current_mode)
return;
this.current_mode = new_mode;
let title = document.getElementById("title"),
username_label = document.getElementById("username-label"),
password_label = document.getElementById("password-label"),
toggle_register_login = document.getElementById("toggle-register-login"),
toggle_register_login_label = document.getElementById("toggle-register-login-label"),
button = document.getElementById("button")
;
let title_text = this.current_mode === "register" ? "registerFormTitle" : "loginFormTitle";
title.innerText = lang.get(title_text, "ERROR LANG");
let username_label_text = this.current_mode === "register" ? "registerFormUsername" : "loginFormUsername";
username_label.innerText = lang.get(username_label_text, "ERROR LANG");
let password_label_text = this.current_mode === "register" ? "registerFormPassword" : "loginFormPassword";
password_label.innerText = lang.get(password_label_text, "ERROR LANG");
let toggle_register_login_label_text = this.current_mode === "register" ? "registerAlreadyAccount" : "loginNoAccount";
toggle_register_login_label.innerText = lang.get(toggle_register_login_label_text, "ERROR LANG");;
let toggle_register_login_text = this.current_mode === "register" ? "registerLogin" : "loginRegister";
toggle_register_login.innerText = lang.get(toggle_register_login_text, "ERROR LANG");
let button_text = this.current_mode === "register" ? "registerFormButton" : "loginFormButton";
button.innerText = lang.get(button_text, "ERROR LANG");
this.titleKey = this.current_mode === 'register' ? 'registerWindowTitle' : 'loginWindowTitle';
this.setTitle();
}
/**
* @returns {Promise}
*/
async authentication()
{
let username = document.getElementById("username-input").value,
password = document.getElementById("password-input").value;
if (!this.basic_verif())
return;
let response;
if (this.current_mode === "register")
response = await client.account.create(username, password)
else
response = await client.login(username, password);
if (response.status === 200 || response.status === 201)
{
navigateTo(this.redirect_url);
return;
}
let response_data = await response.json()
console.log(response_data);
clear("innerHTML", ["username", "password", 'login']);
fill_errors(response_data, "innerHTML");
}
async getHtml()
{
return /* HTML */ `
<div class='container-fluid'>
<form class='border border-2 rounded bg-light-subtle mx-auto p-2 col-md-7 col-lg-4'>
<h4 class='text-center fw-semibold mb-4' id="title">Loading...</h4>
<div class='form-floating mb-2'>
<input type='text' class='form-control' id='username-input' placeholder='Username'>
<label for='usernameInput' id='username-label'>Loading...</label>
<span class='text-danger' id='username'></span>
</div>
<div class='form-floating'>
<input type='password' class='form-control' id='password-input' placeholder='Password'>
<label for='password-input' id='password-label'>Loading...</label>
<span class='text-danger' id='password'></span>
</div>
<div class='d-flex'>
<button type='button' class='btn btn-primary mt-3 mb-2' id='button'>Loading...</button>
<span class='text-danger my-auto mx-2' id='login'></span>
<div class='ms-auto mt-auto flex-row d-flex gap-2' id="toggle">
<p id='toggle-register-login-label'>Loading...</p>
<a id="toggle-register-login" href='#'>Loading...</a>
</div>
</div>
</form>
</div>
`;
}
}

View File

@ -1,79 +0,0 @@
import { client, navigateTo } from "../../index.js";
import { clear, fill_errors } from "../../utils/formUtils.js";
import AbstractNonAuthentifiedView from "../abstracts/AbstractNonAuthentified.js";
async function login(redirectTo = '/home')
{
clear('innerHTML', ['username', 'password', 'login']);
let username = document.getElementById('usernameInput').value;
let password = document.getElementById('passwordInput').value;
if (username === '') {
document.getElementById('username').innerHTML = 'This field may not be blank.';
}
if (password === '') {
document.getElementById('password').innerHTML = 'This field may not be blank.';
}
if (username === '' || password === '')
return;
let response = await client.login(username, password);
if (response.status == 200) {
await client.notice.disconnect();
await client.notice.connect();
navigateTo(redirectTo);
} else {
let error = await response.json();
fill_errors(error, "innerHTML");
}
}
export default class extends AbstractNonAuthentifiedView {
constructor(params, lastUrlBeforeLogin = '/home') {
super(params, "Login", lastUrlBeforeLogin);
this.redirectTo = lastUrlBeforeLogin;
}
async postInit()
{
let usernameField = document.getElementById('usernameInput');
usernameField.addEventListener('keydown', ev => {
if (ev.key === 'Enter')
login(this.redirectTo);
});
usernameField.focus();
let passwordField = document.getElementById('passwordInput');
passwordField.addEventListener('keydown', ev => {
if (ev.key === 'Enter')
login(this.redirectTo);
});
document.getElementById('loginButton').onclick = _ => login(this.redirectTo);
}
async getHtml() {
return `
<div class='container-fluid'>
<form class='border border-2 rounded bg-light-subtle mx-auto p-2 col-md-7 col-lg-4'>
<h4 class='text-center fw-semibold mb-4'>Login</h4>
<div class='form-floating mb-2'>
<input type='text' class='form-control' id='usernameInput' placeholder='Username'>
<label for='usernameInput'>Username</label>
<span class='text-danger' id='username'></span>
</div>
<div class='form-floating'>
<input type='password' class='form-control' id='passwordInput' placeholder='Password'>
<label for='passwordInput'>Password</label>
<span class='text-danger' id='password'></span>
</div>
<div class='d-flex'>
<button type='button' class='btn btn-primary mt-3 mb-2' id='loginButton'>Login</button>
<span class='text-danger my-auto mx-2' id='login'></span>
<p class='ms-auto mt-auto'>No account yet? <a href='/register' data-link>Register</a></p>
</div>
</form>
</div>
`;
}
}

View File

@ -1,10 +1,10 @@
import { client, navigateTo } from "../../index.js"; import { client, navigateTo } from "../../index.js";
import AbstractAuthentifiedView from "../abstracts/AbstractAuthentifiedView.js"; import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthentifiedView export default class extends AbstractAuthenticatedView
{ {
constructor(params, lastPageUrl = '/login') { constructor(params, lastPageUrl = '/login') {
super(params, "Logout"); super(params, 'logoutWindowTitle', lastPageUrl);
this.lastPageUrl = lastPageUrl; this.lastPageUrl = lastPageUrl;
} }

View File

@ -1,77 +0,0 @@
import { client, navigateTo } from "../../index.js";
import { clear, fill_errors } from "../../utils/formUtils.js";
import AbstractNonAuthentifiedView from "../abstracts/AbstractNonAuthentified.js";
async function register(redirectTo = '/home')
{
let username = document.getElementById("usernameInput").value;
let password = document.getElementById("passwordInput").value;
if (username === '' || password === '') {
clear("innerHTML", ["username", "password"]);
if (username === '')
document.getElementById('username').innerHTML = 'This field may not be blank.';
if (password === '')
document.getElementById('password').innerHTML = 'This field may not be blank.';
return;
}
let response_data = await client.account.create(username, password);
if (response_data == null)
{
navigateTo(redirectTo);
return;
}
clear("innerHTML", ["username", "password", 'register']);
fill_errors(response_data, "innerHTML");
}
export default class extends AbstractNonAuthentifiedView {
constructor(params, lastUrlBeforeLogin = '/home') {
super(params, "Register", lastUrlBeforeLogin);
this.redirectTo = lastUrlBeforeLogin;
}
async postInit()
{
let usernameField = document.getElementById('usernameInput');
usernameField.addEventListener('keydown', ev => {
if (ev.key === 'Enter')
register(this.redirectTo);
});
usernameField.focus();
let passwordField = document.getElementById('passwordInput');
passwordField.addEventListener('keydown', ev => {
if (ev.key === 'Enter')
register(this.redirectTo);
});
document.getElementById("registerButton").onclick = _ => register(this.redirectTo);
}
async getHtml() {
return `
<div class='container-fluid'>
<form class='border border-2 rounded bg-light-subtle mx-auto p-2 col-md-7 col-lg-4'>
<h4 class='text-center fw-semibold mb-4'>Register</h4>
<div class='form-floating mb-2'>
<input type='text' class='form-control' id='usernameInput' placeholder='Username'>
<label for='usernameInput'>Username</label>
<span class='text-danger' id='username'></span>
</div>
<div class='form-floating'>
<input type='password' class='form-control' id='passwordInput' placeholder='Password'>
<label for='passwordInput'>Password</label>
<span class='text-danger' id='password'></span>
</div>
<div class='d-flex'>
<button type='button' class='btn btn-primary mt-3 mb-2' id='registerButton'>Register</button>
<span class='text-danger my-auto mx-2' id='register'></span>
<p class='ms-auto mt-auto'>Already have an account? <a href='/login' data-link>Login</a></p>
</div>
</form>
</div>
`;
}
}

View File

@ -1,8 +1,8 @@
import {client, navigateTo} from "../../index.js"; import {client, navigateTo} from "../../index.js";
import { clear, fill_errors } from "../../utils/formUtils.js"; import { clear, fill_errors } from "../../utils/formUtils.js";
import AbstractAuthentifiedView from "../abstracts/AbstractAuthentifiedView.js"; import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthentifiedView export default class extends AbstractAuthenticatedView
{ {
constructor(params) constructor(params)
{ {

View File

@ -1,7 +1,7 @@
import {client, navigateTo} from "../../index.js"; import {client, navigateTo} from "../../index.js";
import AbstractAuthentifiedView from "../abstracts/AbstractAuthentifiedView.js"; import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthentifiedView export default class extends AbstractAuthenticatedView
{ {
constructor(params) constructor(params)
{ {

View File

@ -1,7 +1,7 @@
import {client} from "../../index.js"; import {client} from "../../index.js";
import AbstractAuthentifiedView from "../abstracts/AbstractAuthentifiedView.js"; import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthentifiedView export default class extends AbstractAuthenticatedView
{ {
constructor(params) constructor(params)
{ {

View File

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bozo Pong</title> <title>Bozo Pong</title>
<link rel="stylesheet" href="{% static 'css/bootstrap/bootstrap.min.css' %}"> <link rel="stylesheet" href="{% static 'css/bootstrap/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/index.css' %}">
</head> </head>
<body data-bs-theme="dark"> <body data-bs-theme="dark">
<nav class="navbar navbar-expand-lg bg-body-tertiary rounded m-2"> <nav class="navbar navbar-expand-lg bg-body-tertiary rounded m-2">
@ -17,12 +18,14 @@
</div> </div>
<div class="navbar-nav justify-content-end d-flex flex-row gap-2"> <div class="navbar-nav justify-content-end d-flex flex-row gap-2">
<div class='nav-item dropdown-center me-2'> <div class='nav-item dropdown-center me-2'>
<a class='nav-link dropdown-toggle' role='button' data-bs-toggle='dropdown'> <a id='languageDisplay' class='nav-link dropdown-toggle' role='button' data-bs-toggle='dropdown'>
Lang. Lang.
</a> </a>
<div id='languageSelector' class='dropdown-menu dropdown-menu-end position-absolute text-center px-2' style='min-width: 65px'> <div id='languageSelector' class='dropdown-menu dropdown-menu-end position-absolute text-center px-2' style='min-width: 65px'>
<button value='en' type='button' class="dropdow-item nav-link text-center py-1">&#127468;&#127463; EN</a> <button value='en' type='button' class="dropdown-item nav-link text-center py-1">🇬🇧 EN</button>
<button value='fr' type='button' class="dropdow-item nav-link text-center py-1">&#127467;&#127479; FR</a> <button value='fr' type='button' class="dropdown-item nav-link text-center py-1">🇫🇷 FR</button>
<button value='tp' type='button' class="dropdown-item nav-link text-center py-1">🗣️👍🖼️❤️ toki pona</button>
<button value='cr' type='button' class="dropdown-item nav-link text-center py-1" style="background-image: url(https://image.noelshack.com/fichiers/2023/06/1/1675686620-full.png); background-size: cover; background-position: center; background-repeat: no-repeat; background-color: transparent;">Crampté</button>
</div> </div>
</div> </div>
<div id='navbarLoggedOut' class='d-flex flex-row gap-2'> <div id='navbarLoggedOut' class='d-flex flex-row gap-2'>
@ -35,7 +38,7 @@
</a> </a>
<div class='dropdown-menu dropdown-menu-end position-absolute text-end px-2' style='min-width: 100px'> <div class='dropdown-menu dropdown-menu-end position-absolute text-end px-2' style='min-width: 100px'>
<a data-i18n='navbarProfile' id='myProfileLink' href='' class="dropdow-item nav-link pt-1" data-link>My Profile</a> <a data-i18n='navbarProfile' id='myProfileLink' href='' class="dropdow-item nav-link pt-1" data-link>My Profile</a>
<a data-i18n='navbarSettings' href="/me" class="dropdow-item nav-link pt-0" data-link>Settings</a> <a data-i18n='navbarSettings' href="/settings" class="dropdow-item nav-link pt-0" data-link>Settings</a>
<hr class='dropdown-separator my-auto mx-1'></hr> <hr class='dropdown-separator my-auto mx-1'></hr>
<a data-i18n='navbarLogout' href="/logout" class="dropdow-item nav-link pb-1" data-link>Logout</a> <a data-i18n='navbarLogout' href="/logout" class="dropdow-item nav-link pb-1" data-link>Logout</a>
</div> </div>

View File

@ -1,3 +1,24 @@
from django.test import TestCase import json
from os import listdir
from django.test import Client, TestCase
from django.contrib.staticfiles import finders
# Create your tests here. # Create your tests here.
class DictionnariesTest(TestCase):
def setUp(self) -> None:
self.client = Client();
self.directory = finders.find('js/lang/');
def test_lang(self):
keys = None
json_files = listdir(self.directory);
for file in json_files:
with open(f'{self.directory}/{file}') as f:
data: dict = json.load(f);
if (keys is None):
keys = set(data.keys());
else:
self.assertEqual(set(data.keys()), keys);

View File

@ -12,7 +12,7 @@ MAP_CENTER_Y = MAP_SIZE_Y / 2
WALL_RATIO = 1 WALL_RATIO = 1
BALL_SPEED_INC = 1 BALL_SPEED_INC = 1
BALL_SPEED_START = 1 BALL_SPEED_START = 170
BALL_SIZE = 4 BALL_SIZE = 4
BALL_SPAWN_POS_X = MAP_SIZE_X / 2 BALL_SPAWN_POS_X = MAP_SIZE_X / 2
BALL_SPAWN_POS_Y = MAP_SIZE_Y / 2 BALL_SPAWN_POS_Y = MAP_SIZE_Y / 2

View File

@ -3,22 +3,22 @@ from django.db import models
# Create your models here. # Create your models here.
class GameModel(models.Model): class GameModel(models.Model):
finished = models.BooleanField(default = False) finished = models.BooleanField(default = False)
started = models.BooleanField(default = False) started = models.BooleanField(default = False)
winner_id = models.IntegerField(default = -1) winner_id = models.IntegerField(default = -1)
def create(self, players_id: [int]): def create(self, players_id: [int]):
self.save() self.save()
for player_id in players_id: for player_id in players_id:
GameMembersModel(game_id = self.pk, player_id = player_id).save() GameMembersModel(game_id = self.pk, player_id = player_id).save()
return self.pk return self.pk
def start(self): def start(self):
self.started = True self.started = True
def get_players_id(self): def get_players_id(self):
return [game_player.player_id for game_player in GameMembersModel.objects.filter(game_id = self.pk)] return [game_player.player_id for game_player in GameMembersModel.objects.filter(game_id = self.pk)]
class GameMembersModel(models.Model): class GameMembersModel(models.Model):
game_id = models.IntegerField() game_id = models.IntegerField()
player_id = models.IntegerField() player_id = models.IntegerField()

View File

@ -1,23 +1,36 @@
from __future__ import annotations
from .. import config from .. import config
from .Point import Point
import math
class Ball: class Ball:
def __init__(self) -> None: def __init__(self) -> None:
self.postion_x: float = config.BALL_SPAWN_POS_X self.size: float
self.postion_y: float = config.BALL_SPAWN_POS_Y self.position: Point
self.velocity_x: float = config.BALL_SPEED_START self.angle: float
self.velocity_y: float = config.BALL_SPEED_START self.speed: float
self.size: float = config.BALL_SIZE
self.reset()
def to_dict(self): def reset(self) -> None:
self.size = config.BALL_SIZE
self.position = Point(config.BALL_SPAWN_POS_X + self.size / 2, config.BALL_SPAWN_POS_Y + self.size / 2)
self.angle = math.pi * 1
self.speed = config.BALL_SPEED_START
def to_dict(self) -> dict:
data: dict = { data: dict = {
"size": self.size, "size": self.size,
"position_x": self.postion_x, "speed": self.speed,
"position_y": self.postion_y, "position": self.position.to_dict(),
"velocity_x": self.velocity_x, "angle": self.angle,
"velocity_y": self.velocity_y,
} }
return data return data
def __str__(self) -> str:
return f"Ball(size: {self.size}, speed: {self.speed}, angle: {self.angle}, position: {self.position})"

View File

@ -43,9 +43,9 @@ class Game(AbstractRoom):
for i in range(nb_sides): for i in range(nb_sides):
angle: float = (i * 2 * math.pi / nb_sides) + (math.pi * 3 / nb_sides) angle: float = (i * 2 * math.pi / nb_sides) + (math.pi * 3 / nb_sides)
x: float = config.MAP_CENTER_X + radius * math.cos(angle) x: float = round(config.MAP_CENTER_X + radius * math.cos(angle))
y: float = config.MAP_CENTER_Y + radius * math.sin(angle) y: float = round(config.MAP_CENTER_Y + radius * math.sin(angle))
polygon.append(Point(x, y)) polygon.append(Point(x, y))
@ -107,19 +107,17 @@ class Game(AbstractRoom):
def _player_join(self, user_id: int, socket: WebsocketConsumer): def _player_join(self, user_id: int, socket: WebsocketConsumer):
# check if player is already connected
player = self.get_player_by_user_id(user_id) player = self.get_player_by_user_id(user_id)
if (player is None): if (player is None):
return None return None
# check if player is already connected
if (player.is_connected()): if (player.is_connected()):
player.disconnect(1001) player.disconnect(1001)
player.socket = socket player.socket = socket
if (self.everbody_is_here()): if (self.everbody_is_here()):
print("chie moi dessu")
print("start")
self.start() self.start()
self._update_player(player) self._update_player(player)

View File

@ -1,12 +1,29 @@
from __future__ import annotations
from math import dist
class Point: class Point:
def __init__(self, x: float, y: float) -> None: def __init__(self, x: float, y: float) -> None:
self.x = x self.x = x
self.y = y self.y = y
def __str__(self) -> str:
return f"Point(x: {self.x}, y: {self.y})"
def __repr__(self) -> str:
return f"Point(x: {self.x}, y: {self.x})"
def __eq__(self, __value: object) -> bool:
return (self.x == __value.x and self.y == __value.y)
def distance(self, point: Point):
return dist((point.x, point.y), (self.x, self.y))
def copy(self):
def to_dict(self): return Point(self.x, self.y)
def to_dict(self) -> dict:
data: dict[str: float] = { data: dict[str: float] = {
"x": self.x, "x": self.x,

View File

@ -1,13 +1,34 @@
from .Point import Point from .Point import Point
from .Vector import Vector
import math
class Segment: class Segment:
def __init__(self, start: Point, stop: Point) -> None: def __init__(self, start: Point, stop: Point) -> None:
self.start: Point = start self.start: Point = start
self.stop: Point = stop self.stop: Point = stop
def angle(self):
return math.atan2((self.start.x - self.start.y), (self.stop.x - self.stop.y))
def to_dict(self): def length(self):
return self.start.distance(self.stop)
def is_on(self, C: Point):
return (self.start.distance(C) + self.stop.distance(C) == self.length())
def __repr__(self) -> str:
return f"Segment(start: {self.start}, stop: {self.stop})"
def __str__(self) -> str:
return f"Segment(start: {self.start}, stop: {self.stop})"
def copy(self):
return Segment(self.start.copy(), self.stop.copy())
def to_dict(self) -> dict:
data: dict[str: dict] = { data: dict[str: dict] = {
"start": self.start.to_dict(), "start": self.start.to_dict(),

35
games/objects/Vector.py Normal file
View File

@ -0,0 +1,35 @@
from __future__ import annotations
import math
from .Point import Point
class Vector:
def __init__(self, x: float, y: float) -> None:
self.norm: float = math.dist((0, 0), (x, y))
self.x: float = x
self.y: float = y
def __truediv__(self, denominator: float):
return Vector(self.x / denominator, self.y / denominator)
def angle(self, vector: Vector):
scalar_product: float = self.scalar(vector)
if (scalar_product is None):
return None
cos: float = scalar_product / (vector.norm * self.norm)
angle: float = math.acos(cos)
return angle
def scalar(self, vector: Vector):
return self.x * vector.x + vector.y * self.y
def __str__(self) -> str:
return f"Vector(x: {self.x}, y: {self.y}, norme: {self.norm})"
def __eq__(self, __value: Vector) -> bool:
return (self.x == __value.x and
self.x == __value.x and
self.norm == __value.norm)

View File

@ -9,73 +9,259 @@ if TYPE_CHECKING:
from .objects.Ball import Ball from .objects.Ball import Ball
from .objects.Point import Point from .objects.Point import Point
from .objects.Vector import Point
from .objects.Segment import Segment from .objects.Segment import Segment
from .objects.Vector import Vector
from . import config
from . import config from . import config
import math import math
import asyncio
from asgiref.sync import SyncToAsync
from time import sleep from time import sleep
#see the video to understand the algorithme VERTICALLY = 1
#https://www.youtube.com/watch?v=KOYoMYWUTEo NORMAL = 2
def determine_director_coefficient(segment: Segment):
return ((segment.start.y - segment.stop.y) / (segment.start.x - segment.stop.x))
def determine_ordinate_at_origin(point: Point, director_cofficient: float): def get_sign(num: float) -> int:
return point.y - point.x * director_cofficient if (num == 0):
return 0
if (num > 0):
return 1
if (num < 0):
return -1
def determine_intersection(director_coefficient1: float, ordinate_at_origin1: float, director_coefficient2: float, ordinate_at_origin2: float): def get_derive(segment: Segment) -> float:
if (director_coefficient1 == director_coefficient2):
if (segment.start.x == segment.stop.x):
return None return None
return (ordinate_at_origin1 + ordinate_at_origin2) / (director_coefficient1 + director_coefficient2)
return (segment.stop.y - segment.start.y) / (segment.stop.x - segment.start.x)
def determine_intersections(ball: Ball, segments: list[Segment]): def get_intercept(derive: float, point: Point) -> float:
intersections: list[Point] = [] if (derive is None):
return None
return point.y - (point.x * derive)
def get_constant(segment: Segment) -> float:
return segment.start.x
def identify(segment: Segment) -> str:
if (segment.start.x == segment.stop.x):
return VERTICALLY
return NORMAL
def get_interception(segment1: Segment, segment2: Segment):
if (identify(segment1) == VERTICALLY and identify(segment2) == VERTICALLY):
return None
# because of in matematics world y = 10 is above y = 5 and on a display it is inverted I invert the coordonate
inverted_segment1 = Segment(Point(segment1.start.x, config.MAP_SIZE_Y - segment1.start.y), Point(segment1.stop.x, config.MAP_SIZE_Y - segment1.stop.y))
inverted_segment2 = Segment(Point(segment2.start.x, config.MAP_SIZE_Y - segment2.start.y), Point(segment2.stop.x, config.MAP_SIZE_Y - segment2.stop.y))
if (identify(segment1) == NORMAL and identify(segment2) == NORMAL):
# representation m * x + p
m1 = get_derive(inverted_segment1)
m2 = get_derive(inverted_segment2)
p1 = get_intercept(m1, inverted_segment1.start)
p2 = get_intercept(m2, inverted_segment2.start)
# m1 * x + p1 = m2 * x + p2
# m1 * x = m2 * x + p2 -p1
# m1 * x - m2 * x = p1 - p2
# x * (m1 - m2) = p1 - p2
# x = (p1 - p2) / (m1 - m2)
if (m1 == m2):
return None
# reinvert
x: float = (p1 - p2) / (m1 - m2) * (-1)
y: float = config.MAP_SIZE_Y - (m1 * x + p1)
else:
if (identify(inverted_segment1) == VERTICALLY):
constant: float = get_constant(inverted_segment1)
m: float = get_derive(inverted_segment2)
p: float = get_intercept(m, inverted_segment2.start)
else:
constant: float = get_constant(inverted_segment2)
m: float = get_derive(inverted_segment1)
p: float = get_intercept(m, inverted_segment1.start)
x: float = constant
y: float = config.MAP_SIZE_Y - (m * x + p)
impact: Point = Point(x, y)
return impact
def get_impact_point(segments: list[Segment], ball: Ball) -> dict:
cos: float = round(math.cos(ball.angle), 6)
sin: float = round(math.sin(ball.angle), 6)
point: Point = Point(ball.position.x + cos, ball.position.y - sin)
ball_segment = Segment(ball.position, point)
closest: dict = None
for segment in segments: for segment in segments:
# form m * x + p
m: float = determine_director_coefficient(segment)
p: float = determine_ordinate_at_origin(segment.start, m)
x: float = determine_intersection(m, p, ball.velocity_y, 0)
if (x is None):
continue
y: float = m * x + p
intersections.append(Point(x, y))
return intersections impact: Point = get_interception(segment, ball_segment)
def determine_distance_between_ball_and_wall(ball: Ball, segments: list[Segment]): if (impact is None):
continue
diff_x: float = ball.position.x - impact.x
if (get_sign(diff_x) == get_sign(cos) and cos != 0):
continue
diff_y: float = (ball.position.y - impact.y)
if (get_sign(diff_y) != get_sign(sin) and sin != 0):
continue
impact_with_padding: Point = impact.copy()
impact_with_padding.x += (ball.size / 2) * get_sign(cos) * (-1)
impact_with_padding.y += (ball.size / 2) * get_sign(sin)
if (closest is None or impact_with_padding.distance(ball.position) < closest.get("distance")):
closest = {
"impact_with_padding": impact_with_padding,
"impact": impact,
"segment": segment,
"distance": impact_with_padding.distance(ball.position),
}
return closest
def wall_collision(ball_angle: float, wall_angle: float) -> float:
ball_cos: float = math.cos(ball_angle)
ball_sin: float = math.sin(ball_angle)
incident_angle: float = ball_angle - wall_angle
reflection_angle: float = wall_angle - incident_angle
new_cos: float = math.cos(reflection_angle)
new_sin: float = math.sin(reflection_angle)
new_angle: float = math.atan2(new_sin, new_cos)
return new_angle
def paddle_collision(ball: Ball, impact: Point, player: Player):
diff_x: float = player.rail.stop.x - player.rail.start.x
diff_y: float = player.rail.stop.y - player.rail.start.y
intersections: list[Point] = determine_intersections(ball, segments) paddle_center_x: float = player.rail.start.x + diff_x * player.position.position
paddle_center_y: float = player.rail.start.y + diff_y * player.position.position
distances = list(map(math.dist, intersections)) paddle_center: Point = Point(paddle_center_x, paddle_center_y)
rail_length: float = player.rail.length()
paddle_length: float = rail_length * config.PADDLE_RATIO;
return min(distances) start_x: float = paddle_center.x - (diff_x * (paddle_length / 2 / rail_length))
start_y: float = paddle_center.y - (diff_y * (paddle_length / 2 / rail_length))
stop_x: float = paddle_center.x + (diff_x * (paddle_length / 2 / rail_length))
stop_y: float = paddle_center.y + (diff_y * (paddle_length / 2 / rail_length))
def render(ball: Ball, game: Game): start: Point = Point(start_x, start_y)
stop: Point = Point(stop_x, stop_y)
segments: list[Segment] = [player.rail for player in game.players] paddle: Segment = Segment(start, stop)
print(determine_distance_between_ball_and_wall(ball)) if (not paddle.is_on(impact)):
print(not paddle.is_on(impact))
return None
return ball.angle + math.pi
def collision(game: Game, impact_data: dict) -> bool:
segment: Segment = impact_data.get("segment")
player_hitted = None
for player in game.players:
if (not player.is_connected()):
continue
if (player.rail is segment):
player_hitted = player
break
surface_angle: float = math.atan2(segment.start.y - segment.stop.y, segment.start.x - segment.stop.y)
angle: float
if (player_hitted is None):
angle = wall_collision(game.ball.angle, surface_angle)
else:
angle = paddle_collision(game.ball, impact_data.get("impact"), player_hitted)
if (angle is None):
return False
game.ball.angle = angle
return True
async def update_ball(game: Game, impact_data: dict) -> None:
distance: float = impact_data.get("distance")
time_before_impact: float = distance / game.ball.speed
await asyncio.sleep(time_before_impact)
hit: bool = collision(game, impact_data)
if (hit == False):
await asyncio.sleep(0.1)
print("Goal")
game.ball.reset()
else:
game.ball.position = impact_data.get("impact_with_padding")
await SyncToAsync(game.broadcast)("update_ball", game.ball.to_dict())
async def render(game: Game):
while True:
segments: list[Segment] = [player.rail for player in game.players] + [wall.rail for wall in game.walls]
impact_data: dict = get_impact_point(segments, game.ball)
await update_ball(game, impact_data)
def routine(game: Game): def routine(game: Game):
asyncio.run(render(game))
while True: while True:
for player in game._updated_players: for player in game._updated_players:
game.broadcast("update_paddle", player.to_dict(), [player]) game.broadcast("update_paddle", player.to_dict(), [player])
game._updated_players.clear() game._updated_players.clear()
if (game.started):
game.ball.postion_x = game.ball.postion_x + game.ball.velocity_x
game.ball.postion_y = game.ball.postion_y + game.ball.velocity_y
game.broadcast("update_ball", game.ball.to_dict())
sleep(1 / config.SERVER_TPS) sleep(1 / config.SERVER_TPS)

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/games/(?P<game_id>\d+)$', consumers.GameWebSocket.as_asgi()) re_path(r'ws/games/(?P<game_id>\d+)$', consumers.GameWebSocket.as_asgi())
] ]

View File

@ -8,20 +8,20 @@ from . import config
class GameConfigView(APIView): class GameConfigView(APIView):
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
config_data = { config_data = {
"MAP_SIZE_X": config.MAP_SIZE_X, "MAP_SIZE_X": config.MAP_SIZE_X,
"MAP_SIZE_Y": config.MAP_SIZE_Y, "MAP_SIZE_Y": config.MAP_SIZE_Y,
"WALL_RATIO": config.WALL_RATIO, "WALL_RATIO": config.WALL_RATIO,
"PADDLE_SPEED_PER_SECOND_MAX": config.PADDLE_SPEED_PER_SECOND_MAX, "PADDLE_SPEED_PER_SECOND_MAX": config.PADDLE_SPEED_PER_SECOND_MAX,
"PADDLE_RATIO": config.PADDLE_RATIO, "PADDLE_RATIO": config.PADDLE_RATIO,
"BALL_SIZE": config.BALL_SIZE, "BALL_SIZE": config.BALL_SIZE,
"BALL_SPEED_INC": config.BALL_SPEED_INC, "BALL_SPEED_INC": config.BALL_SPEED_INC,
"BALL_SPEED_START": config.BALL_SPEED_START "BALL_SPEED_START": config.BALL_SPEED_START
} }
return Response(config_data, status = status.HTTP_200_OK) return Response(config_data, status = status.HTTP_200_OK)

View File

@ -10,32 +10,32 @@ from .models import Waiter, WaitingRoom, WaitingRoomManager, normal
class MatchMaking(WebsocketConsumer): class MatchMaking(WebsocketConsumer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.channel_name = "matchmaking" self.channel_name = "matchmaking"
self.group_name = "matchmaking" self.group_name = "matchmaking"
def connect(self): def connect(self):
user: User = self.scope["user"] user: User = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated): if (user.is_anonymous or not user.is_authenticated):
return return
self.channel_layer.group_add(self.group_name, self.channel_name) self.channel_layer.group_add(self.group_name, self.channel_name)
self.mode = int(self.scope['url_route']['kwargs']['mode']) self.mode = int(self.scope['url_route']['kwargs']['mode'])
self.group_name = self.mode self.group_name = self.mode
waiting_room: WaitingRoom = normal.get(self.mode) waiting_room: WaitingRoom = normal.get(self.mode)
waiting_room.append(Waiter(user.pk, self)) waiting_room.append(Waiter(user.pk, self))
waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room.mode}") waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room.mode}")
if (len(waiting_room) == waiting_room.mode): if (len(waiting_room) == waiting_room.mode):
game_id: int = GameModel().create(waiting_room.get_users_id()) game_id: int = GameModel().create(waiting_room.get_users_id())
waiting_room.broadcast("game_found", {"game_id": game_id}) waiting_room.broadcast("game_found", {"game_id": game_id})
waiting_room.clear() waiting_room.clear()
def disconnect(self, close_code): def disconnect(self, close_code):
waiting_room: WaitingRoom = normal.get(self.mode) waiting_room: WaitingRoom = normal.get(self.mode)
waiter: Waiter = waiting_room.get_member_by_socket(self) waiter: Waiter = waiting_room.get_member_by_socket(self)
if (waiter is not None): if (waiter is not None):
waiting_room.remove(waiter, 1016) waiting_room.remove(waiter, 1016)

View File

@ -9,31 +9,31 @@ from transcendence.abstract.AbstractRoomMember import AbstractRoomMember
# Create your models here. # Create your models here.
class Waiter(AbstractRoomMember): class Waiter(AbstractRoomMember):
pass pass
class WaitingRoom(AbstractRoom): class WaitingRoom(AbstractRoom):
def __init__(self, room_manager,mode): def __init__(self, room_manager,mode):
super().__init__(room_manager) super().__init__(room_manager)
self.mode = mode self.mode = mode
def append(self, waiter: Waiter): def append(self, waiter: Waiter):
tmp: Waiter = self.get_member_by_user_id(waiter.user_id) tmp: Waiter = self.get_member_by_user_id(waiter.user_id)
if (tmp is not None): if (tmp is not None):
tmp.send("Connection close: Another connection open with the same user id.") tmp.send("Connection close: Another connection open with the same user id.")
self.remove(tmp) self.remove(tmp)
waiter.accept() waiter.accept()
self._member_list.append(waiter) self._member_list.append(waiter)
class WaitingRoomManager(AbstractRoomManager): class WaitingRoomManager(AbstractRoomManager):
def get(self, mode: int): def get(self, mode: int):
for waiting_room in self._room_list: for waiting_room in self._room_list:
waiting_room: WaitingRoom waiting_room: WaitingRoom
if (waiting_room.mode == mode): if (waiting_room.mode == mode):
return waiting_room return waiting_room
tmp: WaitingRoom = WaitingRoom(self, mode) tmp: WaitingRoom = WaitingRoom(self, mode)
super().append(tmp) super().append(tmp)
return tmp return tmp
normal: WaitingRoomManager = WaitingRoomManager() normal: WaitingRoomManager = WaitingRoomManager()

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/matchmaking/(?P<mode>\d+)$', consumers.MatchMaking.as_asgi()) re_path(r'ws/matchmaking/(?P<mode>\d+)$', consumers.MatchMaking.as_asgi())
] ]

View File

@ -1,7 +1,9 @@
from django.contrib import admin from django.contrib import admin
from .models import ProfileModel, BlockModel from .models import ProfileModel, BlockModel, FriendModel, AskFriendModel
# Register your models here. # Register your models here.
admin.site.register(ProfileModel) admin.site.register(ProfileModel)
admin.site.register(BlockModel) admin.site.register(BlockModel)
admin.site.register(FriendModel)
admin.site.register(AskFriendModel)

View File

@ -14,7 +14,7 @@ def upload_to(instance, filename: str):
# Create your models here. # Create your models here.
class ProfileModel(models.Model): class ProfileModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
avatar_url = models.ImageField(upload_to=upload_to, default="./profiles/static/avatars/default.avif") #blank=True, null=True) avatar = models.ImageField(upload_to=upload_to, default="./profiles/static/avatars/default.avif")
def get_game(self) -> int: def get_game(self) -> int:
for game in game_manager._game_list: for game in game_manager._game_list:
@ -28,10 +28,74 @@ def on_user_created(sender, instance, created, **kwargs):
if created: if created:
profile: ProfileModel = ProfileModel.objects.create(pk = instance.pk, user = instance) profile: ProfileModel = ProfileModel.objects.create(pk = instance.pk, user = instance)
profile.save() profile.save()
class BlockModel(models.Model): class BlockModel(models.Model):
blocker = IntegerField(primary_key=False) blocker = IntegerField(primary_key=False)
blocked = IntegerField(primary_key=False) blocked = IntegerField(primary_key=False)
def __str__(self):
return "blocker_id: " + str(self.blocker) + ", blocked_id: " + str(self.blocked)
class AskFriendModel(models.Model):
asker = IntegerField(primary_key=False)
asked = IntegerField(primary_key=False)
def getAsked(self, asker):
askeds = []
for ask in AskFriendModel.objects.filter(asker=asker):
askeds.append(ask.asked)
return askeds
def getAsker(self, asked):
askers = []
for ask in AskFriendModel.objects.filter(asked=asked):
askers.append(ask.asker)
return askers
def deleteAsk(self, asker, asked):
deleted = AskFriendModel.objects.filter(asker=asker, asked=asked)
if (deleted.count() == 0 or not deleted):
return False
deleted.delete()
return True
class FriendModel(models.Model):
user_id1 = IntegerField(primary_key=False)
user_id2 = IntegerField(primary_key=False)
def getFriends(self, user_id):
friends = []
for friend in FriendModel.objects.filter(user_id1=user_id):
friends.append(friend.user_id2)
for friend in FriendModel.objects.filter(user_id2=user_id):
friends.append(friend.user_id1)
return friends
def isFriend(self, user_id1, user_id2):
return user_id2 in self.getFriends(user_id1)
def deleteFriend(self, user_id1, user_id2):
first = FriendModel.objects.filter(user_id1=user_id1, user_id2=user_id2)
if (first.count() == 1):
first.delete()
return True
second = FriendModel.objects.filter(user_id1=user_id2, user_id2=user_id1)
if (second.count() == 1):
second.delete()
return True
return False
def __str__(self):
return "blocker_id: " + str(self.blocker) + ", blocked_id: " + str(self.blocked)

View File

@ -1,11 +1,20 @@
from rest_framework import serializers from rest_framework import serializers
from .models import ProfileModel from .models import ProfileModel
from django.conf import settings
class ProfileSerializer(serializers.ModelSerializer): class ProfileSerializer(serializers.ModelSerializer):
username = serializers.ReadOnlyField(source='user.username') username = serializers.ReadOnlyField(source='user.username')
avatar_url = serializers.ImageField(required=False) avatar = serializers.ImageField(required=False)
class Meta: class Meta:
model = ProfileModel model = ProfileModel
fields = ["username", "avatar_url", "user_id"] fields = ["username", "avatar", "user_id"]
def validate_avatar(self, value):
'''
Check that the image is not too large
'''
if value.size > settings.PROFILE_PICTURE_MAX_SIZE:
raise serializers.ValidationError('Image is too large.');
return value;

View File

@ -7,7 +7,7 @@ class ProfileTest(TestCase):
def setUp(self): def setUp(self):
self.user: User = User.objects.create(username='bozo', password='password') self.user: User = User.objects.create(username='bozo', password='password')
self.user.save() self.user.save()
self.expected_response = {'avatar_url': '/static/avatars/default.avif', 'user_id': 1, 'username': 'bozo'} self.expected_response = {'avatar': '/static/avatars/default.avif', 'user_id': 1, 'username': 'bozo'}
self.url = "/api/profiles/" self.url = "/api/profiles/"
@ -15,4 +15,4 @@ class ProfileTest(TestCase):
response: HttpResponse = self.client.get(self.url + str(self.user.pk)) response: HttpResponse = self.client.get(self.url + str(self.user.pk))
response_dict: dict = eval(response.content) response_dict: dict = eval(response.content)
self.assertDictEqual(self.expected_response, response_dict) self.assertDictEqual(self.expected_response, response_dict)

View File

@ -1,15 +1,16 @@
from django.urls import path from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from . import viewsets from . import viewsets
from . import views from . import views
urlpatterns = [ urlpatterns = [
path("me", viewsets.MyProfileViewSet.as_view({'patch': 'partial_update', 'get': 'retrieve'}), name="my_profile_page"), path("settings", viewsets.MyProfileViewSet.as_view({'patch': 'partial_update'}), name="my_profile_page"),
path("me", viewsets.MyProfileViewSet.as_view({'get': 'retrieve'}), name="my_profile_page"),
path("", viewsets.ProfileViewSet.as_view({'get': 'list'}), name="profiles_list"), path("", viewsets.ProfileViewSet.as_view({'get': 'list'}), name="profiles_list"),
path("block", views.BlocksView.as_view(), name="block_page"), path("block", views.BlocksView.as_view(), name="block_page"),
path("block/<int:pk>", views.BlockView.as_view(), name="block_page"), path("block/<int:pk>", views.BlockView.as_view(), name="block_page"),
path("<str:username>", viewsets.ProfileViewSet.as_view({'get': 'retrieve'}), name="profile_page"), path("friend", views.FriendsView.as_view(), name="friend_page"),
path("user/<str:username>", viewsets.ProfileViewSet.as_view({'get': 'retrieve'}), name="profile_page"),
path("id/<int:pk>", viewsets.ProfileViewSet.as_view({'get': 'retrieve_id'}), name="profile_page"),
] + static("/static/avatars/", document_root="./avatars") ]

View File

@ -4,62 +4,77 @@ from rest_framework import authentication, permissions, status
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from django.core import serializers from django.core import serializers
from .models import BlockModel from .models import BlockModel, FriendModel
class BlockView(APIView): class BlockView(APIView):
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def get(self, request, pk): def get(self, request, pk):
block = BlockModel.objects.filter(pk=pk) block = BlockModel.objects.filter(pk=pk)
if (block.exists()): if (block.exists()):
return Response(serializers.serialize("json", block), status=status.HTTP_200_OK) return Response(serializers.serialize("json", block), status=status.HTTP_200_OK)
else: else:
return Response("Not Found", status=status.HTTP_404_NOT_FOUND) return Response("Not Found", status=status.HTTP_404_NOT_FOUND)
class BlocksView(APIView): class BlocksView(APIView):
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def get(self, request): def get(self, request):
blocks = BlockModel.objects.filter(blocker=request.user.pk) blocks = BlockModel.objects.filter(blocker=request.user.pk)
if (blocks): if (blocks):
return Response(serializers.serialize("json", BlockModel.objects.filter(blocker=request.user.pk)), status=status.HTTP_200_OK) return Response({"blockeds": serializers.serialize("json", BlockModel.objects.filter(blocker=request.user.pk)), "user_id": request.user.pk}, status=status.HTTP_200_OK)
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
def post(self, request): def post(self, request):
data: dict = request.data data: dict = request.data
users_id = request.data.get("users_id", None) users_id = request.data.get("users_id", None)
if (users_id == None): if (users_id == None):
return Response({"Error"}, status=status.HTTP_400_BAD_REQUEST) return Response({"Error send None"}, status=status.HTTP_400_BAD_REQUEST)
if (BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1])): if (users_id[0] == None or users_id[1] == None):
return Response({"Already Exist"}, status=status.HTTP_409_CONFLICT) return Response({"Error send blocker/ed None"}, status=status.HTTP_400_BAD_REQUEST)
new_block = BlockModel() if (BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1])):
new_block.blocker = users_id[0] return Response({"Already Exist"}, status=status.HTTP_409_CONFLICT)
new_block.blocked = users_id[1]
new_block.save()
return Response({"block_id": new_block.pk}, status=status.HTTP_201_CREATED) new_block = BlockModel()
new_block.blocker = users_id[0]
new_block.blocked = users_id[1]
new_block.save()
def delete(self, request): return Response({"block_id": new_block.pk}, status=status.HTTP_201_CREATED)
data: dict = request.data
users_id = request.data.get("users_id", None)
if (users_id == None): def delete(self, request):
return Response({"Error"}, status=status.HTTP_400_BAD_REQUEST) data: dict = request.data
users_id = request.data.get("users_id", None)
block = BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1]) if (users_id == None):
return Response({"Error"}, status=status.HTTP_400_BAD_REQUEST)
print(list(block)) if (users_id[0] == None or users_id[1] == None):
if (block.count() > 1): return Response({"Error send blocker/ed None"}, status=status.HTTP_400_BAD_REQUEST)
return Response("Not normal >:[", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
if (not block):
return Response("Don't exist", status=status.HTTP_404_NOT_FOUND)
block.delete() block = BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1])
return Response("Deleted", status=status.HTTP_200_OK)
if (block.count() > 1):
return Response("Not normal >:[", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
if (not block):
return Response("Don't exist", status=status.HTTP_404_NOT_FOUND)
block.delete()
return Response("Deleted", status=status.HTTP_200_OK)
class FriendsView(APIView):
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def get(self, request):
friends = FriendModel().getFriends(request.user.pk)
if (friends):
return Response({"friends": friends, "user_id": request.user.pk}, status=status.HTTP_200_OK)
return Response({}, status=status.HTTP_204_NO_CONTENT)

View File

@ -23,14 +23,23 @@ class ProfileViewSet(viewsets.ModelViewSet):
if (not user): if (not user):
return Response({"detail": "Profile not found."}, status=status.HTTP_404_NOT_FOUND) return Response({"detail": "Profile not found."}, status=status.HTTP_404_NOT_FOUND)
instance = self.queryset().get(pk=user[0].pk) instance = self.queryset().get(pk=user[0].pk)
instance.avatar_url.name = instance.avatar_url.name[instance.avatar_url.name.find("static") - 1:] instance.avatar.name = instance.avatar.name[instance.avatar.name.find("static") - 1:]
return Response(self.serializer_class(instance).data,
status=status.HTTP_200_OK)
def retrieve_id(self, request: HttpRequest, pk=None):
user = User.objects.filter(pk=pk)
if (not user):
return Response({"detail": "Profile not found."}, status=status.HTTP_404_NOT_FOUND)
instance = self.queryset().get(pk=user[0].pk)
instance.avatar.name = instance.avatar.name[instance.avatar.name.find("static") - 1:]
return Response(self.serializer_class(instance).data, return Response(self.serializer_class(instance).data,
status=status.HTTP_200_OK) status=status.HTTP_200_OK)
def list(self, request: HttpRequest): def list(self, request: HttpRequest):
serializer = ProfileSerializer(self.queryset(), many=True) serializer = ProfileSerializer(self.queryset(), many=True)
for profile in serializer.data: for profile in serializer.data:
profile["avatar_url"] = profile["avatar_url"][profile["avatar_url"].find("static") - 1:] profile["avatar"] = profile["avatar"][profile["avatar"].find("static") - 1:]
return Response(serializer.data) return Response(serializer.data)
class MyProfileViewSet(viewsets.ModelViewSet): class MyProfileViewSet(viewsets.ModelViewSet):
@ -45,16 +54,18 @@ class MyProfileViewSet(viewsets.ModelViewSet):
return obj return obj
def perform_update(self, serializer, pk=None): def perform_update(self, serializer, pk=None):
profile: ProfileModel = self.get_object() serializer.is_valid(raise_exception=True);
avatar = self.request.data.get("file", None) profile: ProfileModel = self.get_object();
avatar = serializer.validated_data.get('avatar');
if (avatar is not None): if (avatar is not None):
if (profile.avatar_url.name != "./profiles/static/avatars/default.avif"): if (profile.avatar.name != "./profiles/static/avatars/default.avif"):
profile.avatar_url.storage.delete(profile.avatar_url.name) profile.avatar.storage.delete(profile.avatar.name)
profile.avatar_url = avatar profile.avatar = avatar
profile.save() profile.save()
def retrieve(self, request: HttpRequest, pk=None): def retrieve(self, request: HttpRequest, pk=None):
instance: ProfileModel = self.get_object() instance: ProfileModel = self.get_object()
instance.avatar_url.name = instance.avatar_url.name[instance.avatar_url.name.find("static") - 1:] instance.avatar.name = instance.avatar.name[instance.avatar.name.find("static") - 1:]
return Response(self.serializer_class(instance).data, return Response(self.serializer_class(instance).data,
status=status.HTTP_200_OK) status=status.HTTP_200_OK)

1
run.sh
View File

@ -8,4 +8,5 @@ python manage.py makemigrations profiles
python manage.py makemigrations chat python manage.py makemigrations chat
python manage.py makemigrations tournament python manage.py makemigrations tournament
python manage.py migrate python manage.py migrate
python manage.py compilemessages
python manage.py runserver 0.0.0.0:8000 python manage.py runserver 0.0.0.0:8000

View File

@ -10,34 +10,34 @@ from .models import tournament_manager, TournamentMember, TournamentRoom, Tourna
class TournamentWebConsumer(WebsocketConsumer): class TournamentWebConsumer(WebsocketConsumer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.channel_name = "tournament" self.channel_name = "tournament"
self.group_name = "tournament" self.group_name = "tournament"
def connect(self): def connect(self):
self.user: User = self.scope["user"] self.user: User = self.scope["user"]
if (self.user.is_anonymous or not self.user.is_authenticated): if (self.user.is_anonymous or not self.user.is_authenticated):
return return
self.channel_layer.group_add(self.group_name, self.channel_name) self.channel_layer.group_add(self.group_name, self.channel_name)
self.tournament_id = int(self.scope['url_route']['kwargs']['tournament_id']) self.tournament_id = int(self.scope['url_route']['kwargs']['tournament_id'])
self.room = tournament_manager.get(self.tournament_id) self.room = tournament_manager.get(self.tournament_id)
self.member = TournamentMember(self.user.pk, self, self.room) self.member = TournamentMember(self.user.pk, self, self.room)
if (self.room is None): if (self.room is None):
self.member.send("Tournament not found") self.member.send("Tournament not found")
self.disconnect(1017) self.disconnect(1017)
self.room.append(self.member) self.room.append(self.member)
def receive(self, text_data: str = None, bytes_data: bytes = None): def receive(self, text_data: str = None, bytes_data: bytes = None):
self.member.receive(text_data, bytes_data) self.member.receive(text_data, bytes_data)
def disconnect(self, close_code): def disconnect(self, close_code):
member = self.room.get_member_by_socket(self) member = self.room.get_member_by_socket(self)
if (member is not None): if (member is not None):
self.room.remove(self.member, close_code) self.room.remove(self.member, close_code)

View File

@ -13,142 +13,142 @@ from transcendence.abstract.AbstractRoomManager import AbstractRoomManager
# Create your models here.tu # Create your models here.tu
class TournamentModel(models.Model): class TournamentModel(models.Model):
name = models.CharField(max_length = 100) name = models.CharField(max_length = 100)
nb_players = models.IntegerField() nb_players = models.IntegerField()
nb_players_by_game = models.IntegerField() nb_players_by_game = models.IntegerField()
level = models.IntegerField() level = models.IntegerField()
started = models.BooleanField(default = False) started = models.BooleanField(default = False)
finished = models.BooleanField(default = False) finished = models.BooleanField(default = False)
def create_game(self, level, players_id): def create_game(self, level, players_id):
game_id = GameModel().create(players_id = players_id) game_id = GameModel().create(players_id = players_id)
TournamentGamesModel(game_id = game_id, tournament_id = self.pk, tournament_level = level).save() TournamentGamesModel(game_id = game_id, tournament_id = self.pk, tournament_level = level).save()
return game_id return game_id
def get_games_id_by_level(self, level): def get_games_id_by_level(self, level):
tmp = TournamentGamesModel.objects.filter(tournament_id = self.pk, tournament_level = level) tmp = TournamentGamesModel.objects.filter(tournament_id = self.pk, tournament_level = level)
return [instance.game_id for instance in tmp] return [instance.game_id for instance in tmp]
def get_games_id(self): def get_games_id(self):
return [tournament_game.game_id for tournament_game in TournamentGamesModel.objects.filter(tournament_id = self.pk)] return [tournament_game.game_id for tournament_game in TournamentGamesModel.objects.filter(tournament_id = self.pk)]
def get_players_id(self): def get_players_id(self):
return [model.participant_id for model in TournamentParticipantsModel.objects.filter(tournament_id=self.pk)] return [model.participant_id for model in TournamentParticipantsModel.objects.filter(tournament_id=self.pk)]
def is_a_participant(self, participant_id: int): def is_a_participant(self, participant_id: int):
return TournamentParticipantsModel.objects.filter(participant_id = participant_id, tournament_id = self.pk).exists() return TournamentParticipantsModel.objects.filter(participant_id = participant_id, tournament_id = self.pk).exists()
def add_participants(self, participants_id: [int]): def add_participants(self, participants_id: [int]):
for participant_id in participants_id: for participant_id in participants_id:
TournamentParticipantsModel(tournament_id = self.pk, participant_id = participant_id).save() TournamentParticipantsModel(tournament_id = self.pk, participant_id = participant_id).save()
def start(self, participants_id: [int]): def start(self, participants_id: [int]):
self.started = True self.started = True
self.add_participants(participants_id) self.add_participants(participants_id)
games_id = [int] games_id = [int]
for i in range(0, len(participants_id), self.nb_players_by_game): for i in range(0, len(participants_id), self.nb_players_by_game):
game_id = self.create_game(0, participants_id[i : i + self.nb_players_by_game]) game_id = self.create_game(0, participants_id[i : i + self.nb_players_by_game])
games_id.append(game_id) games_id.append(game_id)
self.save() self.save()
return games_id return games_id
class TournamentParticipantsModel(models.Model): class TournamentParticipantsModel(models.Model):
tournament_id = models.IntegerField() tournament_id = models.IntegerField()
participant_id = models.IntegerField() participant_id = models.IntegerField()
class TournamentGamesModel(models.Model): class TournamentGamesModel(models.Model):
tournament_id = models.IntegerField() tournament_id = models.IntegerField()
tournament_level = models.IntegerField() tournament_level = models.IntegerField()
game_id = models.IntegerField() game_id = models.IntegerField()
class TournamentMember(AbstractRoomMember): class TournamentMember(AbstractRoomMember):
def __init__(self, user_id: int, socket: WebsocketConsumer, room): def __init__(self, user_id: int, socket: WebsocketConsumer, room):
super().__init__(user_id, socket) super().__init__(user_id, socket)
self.participate = False self.participate = False
self.room = room self.room = room
def receive(self, text_data: str = None, byte_dates: bytes = None): def receive(self, text_data: str = None, byte_dates: bytes = None):
if (text_data is None): if (text_data is None):
return return
data: dict = json.loads(text_data) data: dict = json.loads(text_data)
if (data.get("participate") is not None): if (data.get("participate") is not None):
self.room.update_participants(self) self.room.update_participants(self)
def send_error_message(self, message: str): def send_error_message(self, message: str):
self.send("error", {"error_message": message}) self.send("error", {"error_message": message})
def go_to(self, url: str): def go_to(self, url: str):
self.send("go_to", {"url": url}) self.send("go_to", {"url": url})
def send_participating(self): def send_participating(self):
self.send("is_participant", {"is_participant": self.participate}) self.send("is_participant", {"is_participant": self.participate})
class TournamentRoom(AbstractRoom): class TournamentRoom(AbstractRoom):
def __init__(self, room_manager, tournament_id: int): def __init__(self, room_manager, tournament_id: int):
super().__init__(room_manager) super().__init__(room_manager)
self.tournament_id = tournament_id self.tournament_id = tournament_id
self.tournament = TournamentModel.objects.get(pk = tournament_id) self.tournament = TournamentModel.objects.get(pk = tournament_id)
def start(self): def start(self):
self.broadcast("tournament_start") self.broadcast("tournament_start")
games_id = self.tournament.start(self.get_participants_id()) games_id = self.tournament.start(self.get_participants_id())
for i, participant in enumerate(self.get_participants()): for i, participant in enumerate(self.get_participants()):
participant: TournamentMember participant: TournamentMember
participant.go_to(f"games/{games_id[i // self.tournament.nb_players_by_game]}") participant.go_to(f"games/{games_id[i // self.tournament.nb_players_by_game]}")
def update_participants(self, member: TournamentMember): def update_participants(self, member: TournamentMember):
if (self.tournament.started): if (self.tournament.started):
member.send_error_message("Tournament already started") member.send_error_message("Tournament already started")
return return
member.participate = not member.participate member.participate = not member.participate
nb_participants = self.get_nb_participants() nb_participants = self.get_nb_participants()
self.broadcast("update_participants", {"nb_participants": nb_participants}) self.broadcast("update_participants", {"nb_participants": nb_participants})
member.send_participating() member.send_participating()
if (nb_participants == self.tournament.nb_players): if (nb_participants == self.tournament.nb_players):
self.start() self.start()
def get_nb_participants(self): def get_nb_participants(self):
if (self.tournament.started): if (self.tournament.started):
return self.tournament.nb_players return self.tournament.nb_players
nb_participants = 0 nb_participants = 0
for member in self._member_list: for member in self._member_list:
member: TournamentMember member: TournamentMember
if (member.participate): if (member.participate):
nb_participants += 1 nb_participants += 1
return nb_participants return nb_participants
def get_participants(self): def get_participants(self):
return [member for member in self._member_list if member.participate] return [member for member in self._member_list if member.participate]
def get_participants_id(self): def get_participants_id(self):
return [member.user_id for member in self._member_list if member.participate] return [member.user_id for member in self._member_list if member.participate]
def append(self, member: TournamentMember): def append(self, member: TournamentMember):
super().append(member) super().append(member)
if self.tournament.started: if self.tournament.started:
member.participate = self.tournament.is_a_participant(member.user_id) member.participate = self.tournament.is_a_participant(member.user_id)
member.send_participating() member.send_participating()
member.send("nb_participants", {"nb_participants": self.get_nb_participants()}) member.send("nb_participants", {"nb_participants": self.get_nb_participants()})
class TournamentRoomManager(AbstractRoomManager): class TournamentRoomManager(AbstractRoomManager):
def get(self, tournament_id: int): def get(self, tournament_id: int):
for room in self._room_list: for room in self._room_list:
if (room.tournament_id == tournament_id): if (room.tournament_id == tournament_id):
return room return room
if (TournamentModel.objects.filter(pk = tournament_id).exists()): if (TournamentModel.objects.filter(pk = tournament_id).exists()):
room = TournamentRoom(self, tournament_id) room = TournamentRoom(self, tournament_id)
self.append(room) self.append(room)
return room return room
return None return None
tournament_manager: TournamentRoomManager = TournamentRoomManager() tournament_manager: TournamentRoomManager = TournamentRoomManager()

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/tournaments/(?P<tournament_id>\d+)$', consumers.TournamentWebConsumer.as_asgi()) re_path(r'ws/tournaments/(?P<tournament_id>\d+)$', consumers.TournamentWebConsumer.as_asgi())
] ]

View File

@ -4,35 +4,35 @@ from games.serializers import GameSerializer
class TournamentSerializer(serializers.ModelSerializer): class TournamentSerializer(serializers.ModelSerializer):
levels = serializers.SerializerMethodField(read_only=True, required=False) levels = serializers.SerializerMethodField(read_only=True, required=False)
level = serializers.ReadOnlyField() level = serializers.ReadOnlyField()
started = serializers.ReadOnlyField() started = serializers.ReadOnlyField()
finished = serializers.ReadOnlyField() finished = serializers.ReadOnlyField()
name = serializers.CharField(default="") name = serializers.CharField(default="")
class Meta: class Meta:
model = TournamentModel model = TournamentModel
fields = ["name", "nb_players", "nb_players_by_game", "level", "started", "finished", "levels", "id"] fields = ["name", "nb_players", "nb_players_by_game", "level", "started", "finished", "levels", "id"]
def get_levels(self, instance: TournamentModel): def get_levels(self, instance: TournamentModel):
levels: [[int]] = [] levels: list[list[int]] = []
for i in range(instance.level): for i in range(instance.level):
games_id: [int] = instance.get_games_id_by_level(i) games_id: [int] = instance.get_games_id_by_level(i)
if (games_id == []): if (games_id == []):
break break
levels.append(games_id) levels.append(games_id)
return levels return levels
def validate_nb_players(self, value: int): def validate_nb_players(self, value: int):
if (value < 2): if (value < 2):
raise serializers.ValidationError("The numbers of players must be greather than 2.") raise serializers.ValidationError("The numbers of players must be greather than 2.")
return value return value
def validate_nb_players_by_game(self, value: int): def validate_nb_players_by_game(self, value: int):
if (value < 2): if (value < 2):
raise serializers.ValidationError("The numbers of players by game must be greather than 2.") raise serializers.ValidationError("The numbers of players by game must be greather than 2.")
nb_players: str = self.initial_data.get("nb_players") nb_players: str = self.initial_data.get("nb_players")
if (nb_players is not None and nb_players.isnumeric()): if (nb_players is not None and nb_players.isnumeric()):
nb_players: int = int(nb_players) nb_players: int = int(nb_players)
if (value > nb_players): if (value > nb_players):
raise serializers.ValidationError("The numbers of players by game must be smaller than the numbers of players.") raise serializers.ValidationError("The numbers of players by game must be smaller than the numbers of players.")
return value return value

View File

@ -9,36 +9,36 @@ import json
import uuid import uuid
class CreateTest(TestCase): class CreateTest(TestCase):
def setUp(self): def setUp(self):
self.client = Client() self.client = Client()
self.url = "/api/tournaments/" self.url = "/api/tournaments/"
self.username = str(uuid.uuid4()) self.username = str(uuid.uuid4())
self.password = str(uuid.uuid4()) self.password = str(uuid.uuid4())
self.nb_players_by_game = 2 self.nb_players_by_game = 2
self.nb_players = 8 self.nb_players = 8
user: User = User.objects.create_user(username=self.username, password=self.password) user: User = User.objects.create_user(username=self.username, password=self.password)
self.client.login(username=self.username, password=self.password) self.client.login(username=self.username, password=self.password)
def test_normal(self): def test_normal(self):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": self.nb_players_by_game, "nb_players": self.nb_players}) response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": self.nb_players_by_game, "nb_players": self.nb_players})
response_data: dict = json.loads(response.content) response_data: dict = json.loads(response.content)
self.assertDictContainsSubset({"name": ""}, response_data) self.assertDictContainsSubset({"name": ""}, response_data)
def test_too_small_nb_players_by_game(self): def test_too_small_nb_players_by_game(self):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 1, "nb_players": self.nb_players}) response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 1, "nb_players": self.nb_players})
response_data = json.loads(response.content) response_data = json.loads(response.content)
self.assertDictEqual(response_data, {'nb_players_by_game': ['The numbers of players by game must be greather than 2.']}) self.assertDictEqual(response_data, {'nb_players_by_game': ['The numbers of players by game must be greather than 2.']})
def test_too_small_nb_players(self): def test_too_small_nb_players(self):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": self.nb_players_by_game, "nb_players": 1}) response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": self.nb_players_by_game, "nb_players": 1})
response_data = json.loads(response.content) response_data = json.loads(response.content)
self.assertDictEqual(response_data, {'nb_players': ['The numbers of players must be greather than 2.'], 'nb_players_by_game': ['The numbers of players by game must be smaller than the numbers of players.']}) self.assertDictEqual(response_data, {'nb_players': ['The numbers of players must be greather than 2.'], 'nb_players_by_game': ['The numbers of players by game must be smaller than the numbers of players.']})
def test_nb_players_smaller_nb_players_by_game(self): def test_nb_players_smaller_nb_players_by_game(self):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 5, "nb_players": 3}) response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 5, "nb_players": 3})
response_data = json.loads(response.content) response_data = json.loads(response.content)
self.assertDictEqual(response_data, {'nb_players_by_game': ['The numbers of players by game must be smaller than the numbers of players.']}) self.assertDictEqual(response_data, {'nb_players_by_game': ['The numbers of players by game must be smaller than the numbers of players.']})

View File

@ -13,44 +13,44 @@ from .serializers import TournamentSerializer
# Create your views here. # Create your views here.
class TournamentViewSet(viewsets.ModelViewSet): class TournamentViewSet(viewsets.ModelViewSet):
queryset = TournamentModel.objects.all queryset = TournamentModel.objects.all
serializer_class = TournamentSerializer serializer_class = TournamentSerializer
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def perform_create(self, serializer: TournamentSerializer): def perform_create(self, serializer: TournamentSerializer):
nb_players = serializer.validated_data["nb_players"] nb_players = serializer.validated_data["nb_players"]
nb_players_by_game = serializer.validated_data["nb_players_by_game"] nb_players_by_game = serializer.validated_data["nb_players_by_game"]
level = 1 level = 1
number: int = nb_players number: int = nb_players
while (number != nb_players_by_game): while (number != nb_players_by_game):
number = number // 2 + (number % 2) number = number // 2 + (number % 2)
level += 1 level += 1
tournament = serializer.save(level = level) tournament = serializer.save(level = level)
return Response(self.serializer_class(tournament).data, status=status.HTTP_201_CREATED) return Response(self.serializer_class(tournament).data, status=status.HTTP_201_CREATED)
def list(self, request: HttpRequest, state: str = ""): def list(self, request: HttpRequest, state: str = ""):
query: QuerySet query: QuerySet
match state: match state:
case "started": case "started":
query = TournamentModel.objects.filter(started=True, finished=False) query = TournamentModel.objects.filter(started=True, finished=False)
case "finished": case "finished":
query = TournamentModel.objects.filter(finished=True) query = TournamentModel.objects.filter(finished=True)
case "waiting": case "waiting":
query = TournamentModel.objects.filter(started=False, finished=False) query = TournamentModel.objects.filter(started=False, finished=False)
case _: case _:
query = TournamentModel.objects.all() query = TournamentModel.objects.all()
serializer = TournamentSerializer(query, many=True) serializer = TournamentSerializer(query, many=True)
return Response(serializer.data) return Response(serializer.data)
def retrieve(self, request: HttpRequest, pk): def retrieve(self, request: HttpRequest, pk):
if (not TournamentModel.objects.filter(pk=pk).exists()): if (not TournamentModel.objects.filter(pk=pk).exists()):
return Response({"detail": "Tournament not found."}, status=status.HTTP_404_NOT_FOUND) return Response({"detail": "Tournament not found."}, status=status.HTTP_404_NOT_FOUND)
tournament = TournamentModel.objects.get(pk=pk) tournament = TournamentModel.objects.get(pk=pk)
return Response(self.serializer_class(tournament).data, status=status.HTTP_200_OK) return Response(self.serializer_class(tournament).data, status=status.HTTP_200_OK)

View File

@ -3,49 +3,49 @@ from channels.generic.websocket import WebsocketConsumer
from .AbstractRoomMember import AbstractRoomMember from .AbstractRoomMember import AbstractRoomMember
class AbstractRoom: class AbstractRoom:
def __init__(self, room_manager): def __init__(self, room_manager):
self._member_list: [AbstractRoomMember] = [] self._member_list: [AbstractRoomMember] = []
self.room_manager = room_manager self.room_manager = room_manager
def broadcast(self, detail: str, data: dict = {}): def broadcast(self, detail: str, data: dict = {}):
for member in self._member_list: for member in self._member_list:
member: AbstractRoomMember member: AbstractRoomMember
member.send(detail, data) member.send(detail, data)
def clear(self): def clear(self):
self._member_list.clear() self._member_list.clear()
def get_member_by_socket(self, socket: WebsocketConsumer): def get_member_by_socket(self, socket: WebsocketConsumer):
for member in self._member_list: for member in self._member_list:
member: AbstractRoomMember member: AbstractRoomMember
if (member.socket is socket): if (member.socket is socket):
return member return member
return None return None
def get_member_by_user_id(self, user_id: int): def get_member_by_user_id(self, user_id: int):
for member in self._member_list: for member in self._member_list:
member: AbstractRoomMember member: AbstractRoomMember
if (member.user_id == user_id): if (member.user_id == user_id):
return member return member
return None return None
def append(self, member: AbstractRoomMember): def append(self, member: AbstractRoomMember):
self._member_list.append(member) self._member_list.append(member)
member.accept() member.accept()
def remove(self, member: AbstractRoomMember, code: int = 1000): def remove(self, member: AbstractRoomMember, code: int = 1000):
self._member_list.remove(member) self._member_list.remove(member)
member.disconnect(code) member.disconnect(code)
def empty(self): def empty(self):
for _ in self._member_list: for _ in self._member_list:
return False return False
return True return True
def get_users_id(self): def get_users_id(self):
return [member.user_id for member in self._member_list] return [member.user_id for member in self._member_list]
def __len__(self): def __len__(self):
return len(self._member_list) return len(self._member_list)

View File

@ -2,11 +2,11 @@ from .AbstractRoom import AbstractRoom
class AbstractRoomManager: class AbstractRoomManager:
def __init__(self): def __init__(self):
self._room_list: [AbstractRoom] = [] self._room_list: [AbstractRoom] = []
def remove(self, room: AbstractRoom): def remove(self, room: AbstractRoom):
self._room_list.remove(room) self._room_list.remove(room)
def append(self, room: AbstractRoom): def append(self, room: AbstractRoom):
self._room_list.append(room) self._room_list.append(room)

View File

@ -4,17 +4,17 @@ import json
class AbstractRoomMember: class AbstractRoomMember:
def __init__(self, user_id: int, socket: WebsocketConsumer): def __init__(self, user_id: int, socket: WebsocketConsumer):
self.user_id: int = user_id self.user_id: int = user_id
self.socket: WebsocketConsumer = socket self.socket: WebsocketConsumer = socket
def send(self, detail: str, data: dict = {}): def send(self, detail: str, data: dict = {}):
raw_data: dict = {"detail": detail} raw_data: dict = {"detail": detail}
raw_data.update(data) raw_data.update(data)
self.socket.send(text_data=json.dumps(raw_data)) self.socket.send(text_data=json.dumps(raw_data))
def accept(self): def accept(self):
self.socket.accept() self.socket.accept()
def disconnect(self, code: int = 1000): def disconnect(self, code: int = 1000):
self.socket.disconnect(code) self.socket.disconnect(code)

View File

@ -22,13 +22,13 @@ 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 + matchmaking.routing.websocket_urlpatterns +
tournament.routing.websocket_urlpatterns + tournament.routing.websocket_urlpatterns +
games.routing.websocket_urlpatterns games.routing.websocket_urlpatterns
) )
) )
}) })

View File

@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
import os import os
from pathlib import Path from pathlib import Path
from django.utils.translation import gettext_lazy as _
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@ -40,8 +41,8 @@ CORS_ORIGIN_WHITELIST = (
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'channels', 'channels',
'daphne', 'daphne',
'tournament.apps.TournamentConfig', 'tournament.apps.TournamentConfig',
'matchmaking.apps.MatchmakingConfig', 'matchmaking.apps.MatchmakingConfig',
@ -64,9 +65,9 @@ INSTALLED_APPS = [
ASGI_APPLICATION = 'transcendence.asgi.application' ASGI_APPLICATION = 'transcendence.asgi.application'
CHANNEL_LAYERS = { CHANNEL_LAYERS = {
'default' :{ 'default' :{
'BACKEND':'channels.layers.InMemoryChannelLayer' 'BACKEND':'channels.layers.InMemoryChannelLayer'
} }
} }
MIDDLEWARE = [ MIDDLEWARE = [
@ -79,6 +80,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
] ]
ROOT_URLCONF = 'transcendence.urls' ROOT_URLCONF = 'transcendence.urls'
@ -137,6 +139,11 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
LANGUAGES = [
('en', _('English')),
('fr', _('French')),
]
TIME_ZONE = 'UTC' TIME_ZONE = 'UTC'
USE_I18N = True USE_I18N = True
@ -153,3 +160,7 @@ STATIC_URL = 'static/'
# 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'
# Profile picture upload limit
PROFILE_PICTURE_MAX_SIZE = 2 * 1024 * 1024 # 2MB