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
**/migrations/**
/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
@ -37,6 +57,10 @@ python manage.py makemigrations chat
python manage.py makemigrations tournament
python manage.py migrate
```
- Compile translations
```
python manage.py compilemessages
```
- Start the developpement server
```
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):
username = CharField()
password = CharField()
username = CharField()
password = CharField()
def get_user(self, data):
user = authenticate(username=data['username'], password=data['password'])
return user
def get_user(self, data):
user = authenticate(username=data['username'], password=data['password'])
return user

View File

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

View File

@ -9,8 +9,8 @@ from ..serializers.login import LoginSerializer
class LoggedView(APIView):
permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,)
permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest):
return Response(status = (status.HTTP_200_OK if request.user.is_authenticated else status.HTTP_400_BAD_REQUEST))
def get(self, request: HttpRequest):
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.contrib.auth import login
from rest_framework.authentication import SessionAuthentication
from django.utils.translation import gettext as _
from ..serializers.login import LoginSerializer
class LoginView(APIView):
permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,)
permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,)
def post(self, request: HttpRequest):
data = request.data
serializer = LoginSerializer(data=data)
serializer.is_valid(raise_exception=True)
user = serializer.get_user(data)
if user is None:
return Response({'login': ['Invalid username or password.']}, status.HTTP_400_BAD_REQUEST)
login(request, user)
return Response({'id': user.pk}, status=status.HTTP_200_OK)
def post(self, request: HttpRequest):
data = request.data
serializer = LoginSerializer(data=data)
serializer.is_valid(raise_exception=True)
user = serializer.get_user(data)
if user is None:
return Response({'login': [_('Invalid username or password.')]}, status.HTTP_401_UNAUTHORIZED)
login(request, user)
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
class LogoutView(APIView):
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest):
logout(request)
return Response("user unlogged", status=status.HTTP_200_OK)
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest):
logout(request)
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
class RegisterView(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request: HttpRequest):
data = request.data
serializer = RegisterSerialiser(data=data)
if serializer.is_valid(raise_exception=True):
user = serializer.create(data)
if user:
login(request, user)
return Response("user created", status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)
permission_classes = (permissions.AllowAny,)
def post(self, request: HttpRequest):
data = request.data
serializer = RegisterSerialiser(data=data)
if serializer.is_valid(raise_exception=True):
user = serializer.create(data)
if user:
login(request, user)
return Response("user created", status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)

View File

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

View File

@ -1,177 +1,379 @@
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
from games.models import GameModel
from profiles.models import FriendModel, AskFriendModel
import time
import json
class ChatNoticeConsumer(WebsocketConsumer):
def connect(self):
def connect(self):
user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated):
#return
user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated):
#return
if (self.channel_layer == None):
return
if (self.channel_layer == None):
return
self.room_group_name = f'chatNotice{user.pk}'
if (not hasattr(self.channel_layer, "users_channels")):
self.channel_layer.users_channels = {}
self.channel_layer.users_channels[user.pk] = self.channel_name
self.room_group_name = f'chatNotice{user.pk}'
if (not hasattr(self.channel_layer, "users_channels")):
self.channel_layer.users_channels = {}
self.channel_layer.users_channels[user.pk] = self.channel_name
if (not hasattr(self.channel_layer, "invite")):
self.channel_layer.invite = {}
self.channel_layer.invite[user.pk] = [];
if (not hasattr(self.channel_layer, "invite")):
self.channel_layer.invite = {}
self.channel_layer.invite[user.pk] = [];
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
self.accept()
message_time: int = int(time.time() * 1000)
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,
})
self.sync()
def disconnect(self, code):
def disconnect(self, code):
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
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())
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,
})
self.sync()
def pre_invite(self, user, targets):
status = 200
def receive(self, text_data=None, bytes_data=None):
for target in targets:
if (target in self.channel_layer.invite[user.pk]):
status = 409
return status
if text_data == None:
return
def invite(self, event):
user = self.scope["user"]
#if (user.is_anonymous or not user.is_authenticated):
#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 (user.pk in self.channel_layer.invite[event["author_id"]]):
return
if (type_notice == None or targets == None):
return
if (user.pk != event["author_id"]):
self.channel_layer.invite[event["author_id"]].append(user.pk)
if (self.channel_layer == None):
return
self.send(text_data=json.dumps({
'type':event['type'],
'author_id':event['author_id'],
'content':event['content'],
'targets': event['targets'],
'time': event['time'],
'status':event['status'],
}))
message_time: int = text_data_json.get('time')
def pre_online_users(self, user, targets):
pass
if (message_time == None):
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 (user.is_anonymous or not user.is_authenticated):
#return
if (status < 300):
if targets == "all":
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({
'type':event['type'],
'author_id':event['author_id'],
'content':event['content'],
'time': event['time'],
'status':event['status'],
}))
def sync(self, user = None, level = None):
sendToUser = True
if (user == None):
user = self.scope["user"]
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.
class ChatChannelModel(models.Model):
def create(self, users_id: [int]):
self.save()
for user_id in users_id:
ChatMemberModel(channel_id = self.pk, member_id = user_id).save()
return self.pk
def create(self, users_id: [int]):
self.save()
for user_id in users_id:
ChatMemberModel(channel_id = self.pk, member_id = user_id).save()
return self.pk
def get_members_id(self):
return [member_channel.member_id for member_channel in ChatMemberModel.objects.filter(channel_id = self.pk)]
def get_members_id(self):
return [member_channel.member_id for member_channel in ChatMemberModel.objects.filter(channel_id = self.pk)]
class ChatMemberModel(models.Model):
member_id = IntegerField(primary_key=False)
channel_id = IntegerField(primary_key=False)
member_id = IntegerField(primary_key=False)
channel_id = IntegerField(primary_key=False)
def __str__(self):
return "member_id: " + str(self.member_id) + ", channel_id: " + str(self.channel_id)
def __str__(self):
return "member_id: " + str(self.member_id) + ", channel_id: " + str(self.channel_id)
class ChatMessageModel(models.Model):
channel_id = IntegerField(primary_key=False)
author_id = IntegerField(primary_key=False)
content = models.CharField(max_length=255)
time = IntegerField(primary_key=False)
channel_id = IntegerField(primary_key=False)
author_id = IntegerField(primary_key=False)
content = models.CharField(max_length=255)
time = IntegerField(primary_key=False)
def __str__(self):
return "author_id: " + str(self.author_id) + ", channel_id: " + str(self.channel_id) + ", content: " + self.content
def __str__(self):
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
websocket_urlpatterns = [
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/(?P<chat_id>\d+)$', consumersChat.ChatConsumer.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.
class ChatTest(TestCase):
def setUp(self):
self.client = Client()
def setUp(self):
self.client = Client()
self.username='bozo1'
self.password='password'
self.username='bozo1'
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):
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_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {})
def test_create_chat(self):
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_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {})
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_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {'detail': 'Authentication credentials were not provided.'})
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_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {'detail': 'Authentication credentials were not provided.'})

View File

@ -14,35 +14,35 @@ from .serializers import ChatChannelSerializer, ChatMessageSerializer
class ChannelView(APIView):
queryset = ChatChannelModel.objects
serializer_class = ChatChannelSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
queryset = ChatChannelModel.objects
serializer_class = ChatChannelSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def post(self, request):
def post(self, request):
serializer = self.serializer_class(data = request.data)
serializer.is_valid(raise_exception=True)
serializer = self.serializer_class(data = request.data)
serializer.is_valid(raise_exception=True)
data: dict = serializer.validated_data
data: dict = serializer.validated_data
members_id = data.get("members_id")
if members_id == None:
return Response({"detail": "members_id is None."}, status = status.HTTP_400_BAD_REQUEST)
members_id = data.get("members_id")
if members_id == None:
return Response({"detail": "members_id is None."}, status = status.HTTP_400_BAD_REQUEST)
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)
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)
for member_channel in ChatMemberModel.objects.filter(member_id = members_id[0]):
channel_id: int = member_channel.channel_id
if not ChatChannelModel.objects.filter(pk = channel_id).exists():
continue
channel: ChatChannelModel = ChatChannelModel.objects.get(pk = channel_id)
if set(channel.get_members_id()) == set(members_id):
messages = ChatMessageModel.objects.filter(channel_id = channel_id).order_by("time")
messages = serializers.serialize("json", messages)
return Response({'channel_id': channel_id, 'messages': messages}, status=status.HTTP_200_OK)
for member_channel in ChatMemberModel.objects.filter(member_id = members_id[0]):
channel_id: int = member_channel.channel_id
if not ChatChannelModel.objects.filter(pk = channel_id).exists():
continue
channel: ChatChannelModel = ChatChannelModel.objects.get(pk = channel_id)
if set(channel.get_members_id()) == set(members_id):
messages = ChatMessageModel.objects.filter(channel_id = channel_id).order_by("time")
messages = serializers.serialize("json", messages)
return Response({'channel_id': channel_id, 'messages': messages}, status=status.HTTP_200_OK)
new_channel_id = ChatChannelModel().create(members_id)
return Response({'channel_id': new_channel_id}, status=status.HTTP_201_CREATED)
new_channel_id = ChatChannelModel().create(members_id)
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 {
max-height: 10em;
max-width: 10em;
@ -34,3 +25,7 @@ body {
opacity: 0;
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
{
font-size: 0.8em;
}
#app #block {
#app #block, #app #friend {
cursor: pointer;
font-size: 0.7em;
text-decoration: underline;
}
#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
{
@ -78,7 +82,6 @@
border: none;
outline: none;
border-bottom: 0.15em solid green;
caret-color: green;
color: green;
font-size: 0.8em;
}
@ -106,9 +109,8 @@
word-wrap: break-word;
}
#app #invite {
#app #invite, #app #yes, #app #no {
position: relative;
background-color: green;
border: none;
color: white;
text-align: center;
@ -118,3 +120,15 @@
width: 4em;
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 {
constructor() {
this.availableLanguages = ['en', 'fr'];
this.availableLanguages = ['en', 'fr', 'tp', 'cr'];
this.dict = null;
this.currentLang = 'en'
this.chosenLang = localStorage.getItem('preferedLanguage') || this.currentLang;
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() {
if (this.currentLang === this.chosenLang)
return;
let dictUrl = `${location.origin}/static/js/lang/${this.chosenLang}.json`;
let translation = await fetch(dictUrl).then(response => {
if (response.status !== 200)
return null;
return response.json();
});
if (!translation) {
console.log(`No translation found for language ${this.chosenLang}`);
await this.loadDict(this.chosenLang);
if (!this.dict)
return 1;
}
document.querySelectorAll('[data-i18n]').forEach(el => {
let key = el.getAttribute('data-i18n');
el.innerHTML = translation[key];
el.innerHTML = this.dict[key];
})
await reloadView();
this.currentLang = this.chosenLang;
return 0;
}
async changeLanguage(lang) {
if (lang === this.currentLang || !this.availableLanguages.includes(lang))
return;
return 1;
this.chosenLang = lang;
if (await this.translatePage() !== 0)
return;
return 1;
this.currentLang = this.chosenLang;
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)
{
super(client, "me")
super(client, "../me")
}
/**
@ -19,7 +19,7 @@ class MyProfile extends Profile
*/
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()
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} password
* @returns {?Promise<Object>}
* @returns {Response}
*/
async create(username, 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);
return null;
}
return response_data
return response;
}
/**

View File

@ -1,3 +1,4 @@
import { navigateTo } from "../../index.js";
import {create_popup} from "../../utils/noticeUtils.js";
class Notice {
@ -5,11 +6,10 @@ class Notice {
this.client = client;
this.data = {};
// users online, invited by, asked friend by,
let data_variable = ["online", "invited", "asked"];
for (let i in data_variable) {
// users online, invited by ..., asked by ..., asker to ...
let data_variable = ["online", "invited", "asked", "asker"];
for (let i in data_variable)
this.data[data_variable[i]] = [];
}
this.connect();
@ -20,15 +20,20 @@ class Notice {
this.chatSocket = new WebSocket(url);
this.chatSocket.onmessage = (event) =>{
let data = JSON.parse(event.data);
//console.log("notice: ", data);
if (data.type == "invite")
this.receiveInvite(data);
else if (data.type == "online_users" || data.type == "disconnect")
this.receiveOnlineUser(data);
let send = JSON.parse(event.data);
//console.log("notice: ", send);
try {
this["receive_" + send.type](send);
}
catch (error) {
console.log("receive_" + send.type + ": Function not found");
}
}
this.chatSocket.onopen = (event) => {
this.getOnlineUser();
this.ask_friend();
}
}
@ -37,64 +42,126 @@ class Notice {
return ;
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) {
if (this.chatSocket == undefined)
return;
async receive_accept_invite(send) {
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",
targets: id_inviteds,
}));
time: new Date().getTime(),
});
}
async receiveInvite(data) {
async receive_invite(send) {
if (data.content === "notice return") {
if (data.status == 200) {
for (let target in data.targets)
this.data["invited"].push(target);
if (this.client.me == undefined)
return ;
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");
}
else if (data.status == 404)
else if (send.status == 444)
return create_popup("User not connected");
else if (data.status == 409)
else if (send.status == 409)
return create_popup("Already invited");
}
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);
// Géré la reception de l'invitation
if (this.rewrite_invite !== undefined)
this.rewrite_invite();
}
}
async getOnlineUser() {
if (this.chatSocket == undefined)
return;
this.online_users = {};
this.chatSocket.send(JSON.stringify({
this.sendRequest({
type: "online_users",
targets: "all",
}));
targets: [],
time: new Date().getTime(),
});
}
async receiveOnlineUser(data) {
if (data.content !== undefined) {
async receive_online_users(send) {
let content = send.online;
if (content !== undefined) {
if (this.online_users.length > 0) {
if (this.data["online"].length > 0) {
// 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
this.data["invited"] = this.data["invited"].filter(id => !disconnects.includes(id));
@ -102,12 +169,139 @@ class Notice {
//console.log(this.data["invited"]);
}
this.data["online"] = Object.keys(data.content);
this.data["online"] = content;
if (this.rewrite_usernames !== undefined)
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}

View File

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

View File

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

View File

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

View File

@ -9,8 +9,32 @@ class Segment
*/
constructor(start, stop)
{
/**
* @type {Point}
*/
this.start = start === undefined ? new Point() : start;
/**
* @type {Point}
*/
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()
{
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()

View File

@ -23,7 +23,7 @@ class MatchMaking
*/
async start(receive_func, disconnect_func, mode)
{
if (!await this.client.isAuthentificate())
if (!await this.client.isAuthenticated())
return null;
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
*/
constructor (client, username, id = undefined, avatar_url = undefined)
constructor (client, username=undefined, id=undefined, avatar_url=undefined)
{
/**
* @type {Client} client
@ -31,6 +31,7 @@ class Profile
* @type {Boolean}
*/
this.isBlocked = false;
this.isFriend = false;
}
/**
@ -39,7 +40,11 @@ class Profile
*/
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)
return response.status;
@ -47,25 +52,49 @@ class Profile
let response_data = await response.json();
this.id = response_data.user_id;
this.username = response_data.username;
this.avatar_url = response_data.avatar_url;
this.avatar_url = response_data.avatar;
if (this.client.me == undefined)
return;
await this.getBlock();
await this.getFriend();
}
async getBlock() {
let block_response = await this.client._get("/api/profiles/block");
if (block_response.status != 200)
return
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 => {
let blocker = block.fields.blocker;
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;
});
}
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}

View File

@ -24,7 +24,7 @@ class Profiles
let profiles = []
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;
}
@ -32,7 +32,7 @@ class Profiles
/**
*
* @param {String} username
* @returns {?Profile}
* @returns {?Promise<Profile>}
*/
async getProfile(username)
{
@ -42,6 +42,14 @@ class Profiles
return profile;
}
async getProfileId(id)
{
let profile = new Profile(this.client, undefined, id);
if (await profile.init())
return null;
return profile;
}
/**
* Block a user
* @param {Number} user_id
@ -51,7 +59,7 @@ class Profiles
// blocker & blocked
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();
@ -68,7 +76,7 @@ class Profiles
// blocker & blocked
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();

View File

@ -120,7 +120,7 @@ class Tourmanent
*/
async join(receive_func, disconnect_func)
{
if (!await this.client.isAuthentificate())
if (!await this.client.isAuthenticated())
return null;
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 LoginView from "./views/accounts/LoginView.js";
import Dashboard from "./views/Dashboard.js";
import Search from "./views/Search.js";
import HomeView from "./views/HomeView.js";
import RegisterView from "./views/accounts/RegisterView.js";
import LogoutView from "./views/accounts/LogoutView.js";
import GameOfflineView from "./views/GameOfflineView.js";
//import GameView from "./views/GameView.js";
@ -13,17 +11,19 @@ import GameView from "./views/GameView3D.js";
import PageNotFoundView from './views/PageNotFoundView.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 MatchMakingView from "./views/MatchMakingView.js";
import TournamentPageView from "./views/tournament/TournamentPageView.js";
import TournamentsView from "./views/tournament/TournamentsListView.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 lastPageUrlBeforeLogin = undefined
let lastView = undefined;
let lastPageUrlBeforeLogin = undefined;
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
@ -37,11 +37,12 @@ const getParams = match => {
};
const navigateTo = async (uri) => {
if (await router(uri) !== 0)
return;
history.pushState(null, null, uri);
if (await router(uri) !== 0)
return;
let link = document.querySelector('a[href=\'' + location.pathname + '\']');
if (link) {
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)
{
let content = await view.getHtml();
let content = await view?.getHtml();
if (content == null)
return 1;
@ -74,12 +80,12 @@ const router = async(uri) => {
{ path: "/tournaments/create", view: TournamentCreateView },
{ path: "/tournaments/:id", view: TournamentPageView },
{ path: "/tournaments/", view: TournamentsView },
{ path: "/login", view: LoginView },
{ path: "/login", view: AuthenticationView },
{ path: "/register", view: AuthenticationView },
{ path: "/logout", view: LogoutView },
{ path: "/register", view: RegisterView },
{ path: "/search", view: Search },
{ path: "/home", view: HomeView },
{ path: "/me", view: MeView },
{ path: "/settings", view: SettingsView },
{ path: "/matchmaking", view: MatchMakingView },
{ path: "/games/offline", view: GameOfflineView },
{ path: "/games/:id", view: GameView },
@ -110,12 +116,13 @@ const router = async(uri) => {
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())
return 1;
lastView = view;
if (uri !== '/login' && uri !== '/register' && uri !== '/logout')
lastPageUrlBeforeLogin = uri;
if (await renderView(view))
return 1;
@ -138,13 +145,22 @@ document.addEventListener("DOMContentLoaded", async () => {
});
//Languages
await lang.waitLoading();
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);
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",
"navbarProfile": "My Profile",
"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",
"navbarHome": "Maison",
"navbarLogin": "Se connecter",
"navbarLogin": "Connexion",
"navbarRegister": "S'inscrire",
"navbarProfile": "Mon Profil",
"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);
}
async leavePage() {
this.game?.cleanup();
}
startGame() {
if (this.game == null) {
document.getElementById('startGameButton').innerHTML = 'Reset Game';
@ -26,6 +30,7 @@ export default class extends AbstractView {
}
else {
document.getElementById('app').removeChild(this.game.canvas);
document.getElementById('app').removeChild(this.game.scoresDisplay);
this.game.cleanup();
this.game = new Game;
}

View File

@ -13,7 +13,7 @@ export default class extends AbstractView
this.my_player = undefined;
}
keyStretchHandler(event)
keyReleaseHandler(event)
{
const idx = this.keys_pressed.indexOf(event.key);
if (idx != -1)
@ -26,7 +26,7 @@ export default class extends AbstractView
this.keys_pressed.push(event.key);
}
draw()
render_game()
{
const canva = document.getElementById('canva');
@ -40,36 +40,39 @@ export default class extends AbstractView
ctx.beginPath();
this.game.draw(ctx);
this.game.render(ctx);
ctx.strokeStyle = "#000000";
ctx.lineWidth = 10;
ctx.lineWidth = 1;
ctx.stroke();
}
render_game()
render()
{
let loop_id = setInterval(() => {
if (this.game === undefined)
clearInterval(loop_id);
if (this.my_player)
this.my_player.update_paddle(this.keys_pressed);
this.draw();
this.render_game();
this.game?.time.new_frame();
//clearInterval(loop_id);
// 1 sec fps
}, 1000 / 60);
}
register_key()
{
document.addEventListener('keydown', this.keyPressHandler.bind(this));
document.addEventListener('keyup', this.keyStretchHandler.bind(this));
this.keyPressHandler = this.keyPressHandler.bind(this);
this.keyReleaseHandler = this.keyReleaseHandler.bind(this);
document.addEventListener('keydown', this.keyPressHandler);
document.addEventListener('keyup', this.keyReleaseHandler);
}
unregister_key()
{
document.removeEventListener('keydown', this.keyPressHandler);
document.removeEventListener('keyup', this.keyStretchHandler);
document.removeEventListener('keyup', this.keyReleaseHandler);
}
async join_game()
@ -99,7 +102,7 @@ export default class extends AbstractView
this.register_key()
this.render_game();
this.render();
}
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) {
super(params, "Home");
super(params, 'homeWindowTitle');
this.redirect_url = "/login"
}
async getHtml() {
return /* HTML */ `
<h1>HOME</h1>
<a href="/matchmaking" class="nav__link" data-link>Play a game</a>
<a href="/games/offline" class="nav__link" data-link>Play offline</a>
<a href="/me" class="nav__link" data-link>Me</a>
<a href="/logout" class="nav__link" data-link>Logout</a>
<h1>${lang.get('homeTitle', 'Home')}</h1>
<a href="/matchmaking" data-link>${lang.get('homeOnline', 'Play online')}</a>
<a href="/games/offline" data-link>${lang.get('homeOffline', 'Play offline')}</a>
<a href="/settings" data-link>${lang.get('homeSettings', 'Settings')}</a>
<a href="/logout" data-link>${lang.get('homeLogout', 'Logout')}</a>
`;
}
}

View File

@ -1,8 +1,8 @@
import { client, navigateTo } from "../index.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)
{
super(params, "Matchmaking");

View File

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

View File

@ -1,25 +1,26 @@
import AbstractView from "./abstracts/AbstractView.js";
import { client } from "../index.js"
import { client, lang } from "../index.js"
export default class extends AbstractView {
constructor(params) {
super(params, params.username);
this.username = params.username;
super(params, decodeURI(params.username));
this.username = decodeURI(params.username);
}
async postInit()
{
this.profile = await client.profiles.getProfile(this.username);
if (this.profile === null)
return 404;
this.userId = this.profile.id;
this.user_id = this.profile.id;
this.info = document.getElementById("info");
// Username
let username = document.createElement("a");
username.id = "username";
username.appendChild(document.createTextNode(this.profile.username));
username.appendChild(document.createTextNode(this.username));
this.info.appendChild(username);
this.info.appendChild(document.createElement("br"));
@ -31,30 +32,97 @@ export default class extends AbstractView {
this.info.appendChild(avatar);
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() {
// Block option
if (await client.isAuthentificate() === false)
if (await client.isAuthenticated() === false)
return;
if (client.me.id != this.userId) {
let block = document.getElementById("block") || document.createElement("a");
if (client.me.id != this.user_id) {
let block = document.getElementById("block");
if (block == undefined) {
block = document.createElement("p");
this.info.appendChild(block);
}
block.id = "block";
block.innerText = "";
block.onclick = async () => {
if (!this.profile.isBlocked)
await client.profiles.block(this.userId);
await client.profiles.block(this.user_id);
else
await client.profiles.deblock(this.userId);
await client.profiles.deblock(this.user_id);
this.profile = await client.profiles.getProfile(this.username);
this.blockButton();
};
if (this.profile.isBlocked)
block.appendChild(document.createTextNode("Deblock"));
block.textContent = lang.get('profileUnblock', 'Unblock');
else
block.appendChild(document.createTextNode("Block"));
this.info.appendChild(block);
block.textContent = lang.get('profileBlock', '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 {client} from "../index.js";
import { client, lang } from "../index.js";
import {Message} from "../api/chat/message.js"
export default class extends AbstractView {
constructor(params) {
super(params, "Search");
super(params, 'SearchWindowTitle');
}
async wait_get_online_users() {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
//console.log(client.notice.data["online"].length);
if (client.notice.data["online"].length > 0) {
console.log(client.notice.data["online"]);
if (Object.keys(client.notice.data["online"]).length > 0) {
clearInterval(checkInterval);
resolve();
}
}, 100);
}, 1);
});
}
async postInit() {
let logged = await client.isAuthentificate();
let logged = await client.isAuthenticated();
let profiles = await client.profiles.all();
//console.log(client.notice.data);
@ -29,11 +29,13 @@ export default class extends AbstractView {
return console.log("Error");
//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_invite = this.display_invite;
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");
//chat_input.addEventListener("keydown", this.display_chat_manager)
@ -66,7 +68,12 @@ export default class extends AbstractView {
username.setAttribute('data-link', '');
username.id = `username${user.id}`
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));
new_user.appendChild(username);
@ -135,8 +142,14 @@ export default class extends AbstractView {
profiles.filter(user => user.username.toLowerCase().startsWith(search) == true).forEach((user) => {
let username = document.getElementById(`username${user.id}`);
if (username !== null)
username.style.color = client.notice.data["online"].includes(user.id.toString()) ? "green" : "red";
if (username !== null) {
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.appendChild(chat_input);
let members_id = client.channels.channel.members_id;
chat_input.onkeydown = async () => {
if (event.keyCode == 13 && client.channels.channel != undefined) {
//let chat_input = document.getElementById("input_chat");
let chat_text = chat_input.value;
let receivers_id = [];
client.channels.channel.members_id.forEach((member_id) => {
members_id.forEach((member_id) => {
if (member_id != client.me.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
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) {
let members_id = client.channels.channel.members_id;
let members = document.createElement("h2");
members.id = "members";
let usernames = "";
client.channels.channel.members_id.forEach((member_id) => {
members_id.forEach((member_id) => {
if (member_id != client.me.id) {
if (usernames.length > 0)
usernames += ", ";
usernames += (profiles.filter(user => user.id == member_id)[0].username);
}
});
members.appendChild(document.createTextNode(usernames));
members.textContent = usernames;
chat.appendChild(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");
invite.id = "invite";
invite.innerText = "invite";
invite.onclick = async () => {
await client.notice.sendInvite(client.me.id,
client.channels.channel.members_id.filter(id => id !== client.me.id));
};
chat.appendChild(invite);
let yes = document.getElementById("yes") || document.createElement("button");
let no = document.getElementById("no") || document.createElement("button");
let invitedBy = undefined;
for (let x in others) {
if (client.notice.data["invited"].includes(others[x])) {
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 { 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)
{
super(params, "Me");
super(params, "Settings");
this.PROFILE_PICTURE_MAX_SIZE = 2 * 1024 * 1024; // 2MB
}
async postInit()
@ -24,7 +25,7 @@ export default class extends AbstractAuthentificateView
document.getElementById("avatar").remove();
let avatar = document.createElement("img");
avatar.id = "avatar";
avatar.src = profile.avatar_url;
avatar.src = profile.avatar_url + '?t=' +new Date().getTime();
document.getElementsByClassName("avatar")[0].appendChild(avatar);
}
}
@ -35,7 +36,7 @@ export default class extends AbstractAuthentificateView
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")
{
navigateTo("/login");
@ -78,18 +79,24 @@ export default class extends AbstractAuthentificateView
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();
form_data.append("file", avatar.files[0]);
form_data.append("avatar", avatar.files[0]);
await client.me.change_avatar(form_data);
this.display_avatar();
}
document.getElementById("save-profile").classList.remove('text-danger');
document.getElementById("save-profile").innerHTML = "Saved";
}
async getHtml()
{
return /* HTML */ `
<link rel="stylesheet" href="/static/css/me.css">
<link rel="stylesheet" href="/static/css/settings.css">
<h1>ME</h1>
<div id="main">
<div class="avatar">

View File

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

View File

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

View File

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

View File

@ -1,7 +1,9 @@
import {lang} from '../../index.js'
export default class {
constructor(params, title) {
constructor(params, titleKey) {
this.params = params;
this.title = title;
this.titleKey = titleKey;
}
async postInit() {
@ -11,7 +13,7 @@ export default class {
}
setTitle() {
document.title = this.title;
document.title = lang.get(this.titleKey, 'Bozo Pong');
}
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 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') {
super(params, "Logout");
super(params, 'logoutWindowTitle', 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 { 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)
{

View File

@ -1,7 +1,7 @@
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)
{

View File

@ -1,7 +1,7 @@
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)
{

View File

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bozo Pong</title>
<link rel="stylesheet" href="{% static 'css/bootstrap/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/index.css' %}">
</head>
<body data-bs-theme="dark">
<nav class="navbar navbar-expand-lg bg-body-tertiary rounded m-2">
@ -17,12 +18,14 @@
</div>
<div class="navbar-nav justify-content-end d-flex flex-row gap-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.
</a>
<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='fr' type='button' class="dropdow-item nav-link text-center py-1">&#127467;&#127479; FR</a>
<button value='en' type='button' class="dropdown-item nav-link text-center py-1">🇬🇧 EN</button>
<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 id='navbarLoggedOut' class='d-flex flex-row gap-2'>
@ -35,7 +38,7 @@
</a>
<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='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>
<a data-i18n='navbarLogout' href="/logout" class="dropdow-item nav-link pb-1" data-link>Logout</a>
</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.
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
BALL_SPEED_INC = 1
BALL_SPEED_START = 1
BALL_SPEED_START = 170
BALL_SIZE = 4
BALL_SPAWN_POS_X = MAP_SIZE_X / 2
BALL_SPAWN_POS_Y = MAP_SIZE_Y / 2

View File

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

View File

@ -1,23 +1,36 @@
from __future__ import annotations
from .. import config
from .Point import Point
import math
class Ball:
def __init__(self) -> None:
self.postion_x: float = config.BALL_SPAWN_POS_X
self.postion_y: float = config.BALL_SPAWN_POS_Y
self.velocity_x: float = config.BALL_SPEED_START
self.velocity_y: float = config.BALL_SPEED_START
self.size: float = config.BALL_SIZE
self.size: float
self.position: Point
self.angle: float
self.speed: float
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 = {
"size": self.size,
"position_x": self.postion_x,
"position_y": self.postion_y,
"velocity_x": self.velocity_x,
"velocity_y": self.velocity_y,
"speed": self.speed,
"position": self.position.to_dict(),
"angle": self.angle,
}
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):
angle: float = (i * 2 * math.pi / nb_sides) + (math.pi * 3 / nb_sides)
x: float = config.MAP_CENTER_X + radius * math.cos(angle)
y: float = config.MAP_CENTER_Y + radius * math.sin(angle)
x: float = round(config.MAP_CENTER_X + radius * math.cos(angle))
y: float = round(config.MAP_CENTER_Y + radius * math.sin(angle))
polygon.append(Point(x, y))
@ -107,19 +107,17 @@ class Game(AbstractRoom):
def _player_join(self, user_id: int, socket: WebsocketConsumer):
# check if player is already connected
player = self.get_player_by_user_id(user_id)
if (player is None):
return None
# check if player is already connected
if (player.is_connected()):
player.disconnect(1001)
player.socket = socket
if (self.everbody_is_here()):
print("chie moi dessu")
print("start")
self.start()
self._update_player(player)

View File

@ -1,12 +1,29 @@
from __future__ import annotations
from math import dist
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
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] = {
"x": self.x,

View File

@ -1,13 +1,34 @@
from .Point import Point
from .Vector import Vector
import math
class Segment:
def __init__(self, start: Point, stop: Point) -> None:
self.start: Point = start
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] = {
"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.Point import Point
from .objects.Vector import Point
from .objects.Segment import Segment
from .objects.Vector import Vector
from . import config
from . import config
import math
import asyncio
from asgiref.sync import SyncToAsync
from time import sleep
#see the video to understand the algorithme
#https://www.youtube.com/watch?v=KOYoMYWUTEo
def determine_director_coefficient(segment: Segment):
return ((segment.start.y - segment.stop.y) / (segment.start.x - segment.stop.x))
VERTICALLY = 1
NORMAL = 2
def determine_ordinate_at_origin(point: Point, director_cofficient: float):
return point.y - point.x * director_cofficient
def get_sign(num: float) -> int:
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):
if (director_coefficient1 == director_coefficient2):
def get_derive(segment: Segment) -> float:
if (segment.start.x == segment.stop.x):
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:
# 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):
asyncio.run(render(game))
while True:
for player in game._updated_players:
game.broadcast("update_paddle", player.to_dict(), [player])
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)

View File

@ -2,5 +2,5 @@ from django.urls import re_path
from . import consumers
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):
permission_classes = (permissions.AllowAny,)
permission_classes = (permissions.AllowAny,)
def get(self, request: HttpRequest):
config_data = {
"MAP_SIZE_X": config.MAP_SIZE_X,
"MAP_SIZE_Y": config.MAP_SIZE_Y,
def get(self, request: HttpRequest):
config_data = {
"MAP_SIZE_X": config.MAP_SIZE_X,
"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_RATIO": config.PADDLE_RATIO,
"PADDLE_SPEED_PER_SECOND_MAX": config.PADDLE_SPEED_PER_SECOND_MAX,
"PADDLE_RATIO": config.PADDLE_RATIO,
"BALL_SIZE": config.BALL_SIZE,
"BALL_SPEED_INC": config.BALL_SPEED_INC,
"BALL_SPEED_START": config.BALL_SPEED_START
}
return Response(config_data, status = status.HTTP_200_OK)
"BALL_SIZE": config.BALL_SIZE,
"BALL_SPEED_INC": config.BALL_SPEED_INC,
"BALL_SPEED_START": config.BALL_SPEED_START
}
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):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.channel_name = "matchmaking"
self.group_name = "matchmaking"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.channel_name = "matchmaking"
self.group_name = "matchmaking"
def connect(self):
def connect(self):
user: User = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
user: User = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
self.channel_layer.group_add(self.group_name, self.channel_name)
self.channel_layer.group_add(self.group_name, self.channel_name)
self.mode = int(self.scope['url_route']['kwargs']['mode'])
self.group_name = self.mode
self.mode = int(self.scope['url_route']['kwargs']['mode'])
self.group_name = self.mode
waiting_room: WaitingRoom = normal.get(self.mode)
waiting_room.append(Waiter(user.pk, self))
waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room.mode}")
if (len(waiting_room) == waiting_room.mode):
game_id: int = GameModel().create(waiting_room.get_users_id())
waiting_room.broadcast("game_found", {"game_id": game_id})
waiting_room.clear()
waiting_room: WaitingRoom = normal.get(self.mode)
waiting_room.append(Waiter(user.pk, self))
waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room.mode}")
if (len(waiting_room) == waiting_room.mode):
game_id: int = GameModel().create(waiting_room.get_users_id())
waiting_room.broadcast("game_found", {"game_id": game_id})
waiting_room.clear()
def disconnect(self, close_code):
waiting_room: WaitingRoom = normal.get(self.mode)
waiter: Waiter = waiting_room.get_member_by_socket(self)
if (waiter is not None):
waiting_room.remove(waiter, 1016)
def disconnect(self, close_code):
waiting_room: WaitingRoom = normal.get(self.mode)
waiter: Waiter = waiting_room.get_member_by_socket(self)
if (waiter is not None):
waiting_room.remove(waiter, 1016)

View File

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

View File

@ -2,5 +2,5 @@ from django.urls import re_path
from . import consumers
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 .models import ProfileModel, BlockModel
from .models import ProfileModel, BlockModel, FriendModel, AskFriendModel
# Register your models here.
admin.site.register(ProfileModel)
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.
class ProfileModel(models.Model):
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:
for game in game_manager._game_list:
@ -28,10 +28,74 @@ def on_user_created(sender, instance, created, **kwargs):
if created:
profile: ProfileModel = ProfileModel.objects.create(pk = instance.pk, user = instance)
profile.save()
class BlockModel(models.Model):
blocker = IntegerField(primary_key=False)
blocked = IntegerField(primary_key=False)
blocker = 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 .models import ProfileModel
from django.conf import settings
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.ReadOnlyField(source='user.username')
avatar_url = serializers.ImageField(required=False)
avatar = serializers.ImageField(required=False)
class Meta:
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):
self.user: User = User.objects.create(username='bozo', password='password')
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/"
@ -15,4 +15,4 @@ class ProfileTest(TestCase):
response: HttpResponse = self.client.get(self.url + str(self.user.pk))
response_dict: dict = eval(response.content)
self.assertDictEqual(self.expected_response, response_dict)

View File

@ -1,15 +1,16 @@
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from . import viewsets
from . import views
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("block", views.BlocksView.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("block", views.BlocksView.as_view(), name="block_page"),
path("block/<int:pk>", views.BlockView.as_view(), name="block_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.renderers import JSONRenderer
from django.core import serializers
from .models import BlockModel
from .models import BlockModel, FriendModel
class BlockView(APIView):
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def get(self, request, pk):
block = BlockModel.objects.filter(pk=pk)
if (block.exists()):
return Response(serializers.serialize("json", block), status=status.HTTP_200_OK)
else:
return Response("Not Found", status=status.HTTP_404_NOT_FOUND)
def get(self, request, pk):
block = BlockModel.objects.filter(pk=pk)
if (block.exists()):
return Response(serializers.serialize("json", block), status=status.HTTP_200_OK)
else:
return Response("Not Found", status=status.HTTP_404_NOT_FOUND)
class BlocksView(APIView):
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def get(self, request):
blocks = BlockModel.objects.filter(blocker=request.user.pk)
if (blocks):
return Response(serializers.serialize("json", BlockModel.objects.filter(blocker=request.user.pk)), status=status.HTTP_200_OK)
return Response({}, status=status.HTTP_204_NO_CONTENT)
def get(self, request):
blocks = BlockModel.objects.filter(blocker=request.user.pk)
if (blocks):
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)
def post(self, request):
data: dict = request.data
users_id = request.data.get("users_id", None)
def post(self, request):
data: dict = request.data
users_id = request.data.get("users_id", None)
if (users_id == None):
return Response({"Error"}, status=status.HTTP_400_BAD_REQUEST)
if (users_id == None):
return Response({"Error send None"}, status=status.HTTP_400_BAD_REQUEST)
if (BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1])):
return Response({"Already Exist"}, status=status.HTTP_409_CONFLICT)
if (users_id[0] == None or users_id[1] == None):
return Response({"Error send blocker/ed None"}, status=status.HTTP_400_BAD_REQUEST)
new_block = BlockModel()
new_block.blocker = users_id[0]
new_block.blocked = users_id[1]
new_block.save()
if (BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1])):
return Response({"Already Exist"}, status=status.HTTP_409_CONFLICT)
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):
data: dict = request.data
users_id = request.data.get("users_id", None)
return Response({"block_id": new_block.pk}, status=status.HTTP_201_CREATED)
if (users_id == None):
return Response({"Error"}, status=status.HTTP_400_BAD_REQUEST)
def delete(self, 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 (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)
if (users_id[0] == None or users_id[1] == None):
return Response({"Error send blocker/ed None"}, status=status.HTTP_400_BAD_REQUEST)
block.delete()
return Response("Deleted", status=status.HTTP_200_OK)
block = BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1])
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):
return Response({"detail": "Profile not found."}, status=status.HTTP_404_NOT_FOUND)
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,
status=status.HTTP_200_OK)
def list(self, request: HttpRequest):
serializer = ProfileSerializer(self.queryset(), many=True)
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)
class MyProfileViewSet(viewsets.ModelViewSet):
@ -45,16 +54,18 @@ class MyProfileViewSet(viewsets.ModelViewSet):
return obj
def perform_update(self, serializer, pk=None):
profile: ProfileModel = self.get_object()
avatar = self.request.data.get("file", None)
serializer.is_valid(raise_exception=True);
profile: ProfileModel = self.get_object();
avatar = serializer.validated_data.get('avatar');
if (avatar is not None):
if (profile.avatar_url.name != "./profiles/static/avatars/default.avif"):
profile.avatar_url.storage.delete(profile.avatar_url.name)
profile.avatar_url = avatar
profile.save()
if (profile.avatar.name != "./profiles/static/avatars/default.avif"):
profile.avatar.storage.delete(profile.avatar.name)
profile.avatar = avatar
profile.save()
def retrieve(self, request: HttpRequest, pk=None):
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,
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 tournament
python manage.py migrate
python manage.py compilemessages
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):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.channel_name = "tournament"
self.group_name = "tournament"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.channel_name = "tournament"
self.group_name = "tournament"
def connect(self):
def connect(self):
self.user: User = self.scope["user"]
if (self.user.is_anonymous or not self.user.is_authenticated):
return
self.user: User = self.scope["user"]
if (self.user.is_anonymous or not self.user.is_authenticated):
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.member = TournamentMember(self.user.pk, self, self.room)
self.room = tournament_manager.get(self.tournament_id)
self.member = TournamentMember(self.user.pk, self, self.room)
if (self.room is None):
self.member.send("Tournament not found")
self.disconnect(1017)
self.room.append(self.member)
if (self.room is None):
self.member.send("Tournament not found")
self.disconnect(1017)
self.room.append(self.member)
def receive(self, text_data: str = None, bytes_data: bytes = None):
self.member.receive(text_data, bytes_data)
def receive(self, text_data: str = None, bytes_data: bytes = None):
self.member.receive(text_data, bytes_data)
def disconnect(self, close_code):
member = self.room.get_member_by_socket(self)
if (member is not None):
self.room.remove(self.member, close_code)
def disconnect(self, close_code):
member = self.room.get_member_by_socket(self)
if (member is not None):
self.room.remove(self.member, close_code)

View File

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

View File

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

View File

@ -9,36 +9,36 @@ import json
import uuid
class CreateTest(TestCase):
def setUp(self):
self.client = Client()
def setUp(self):
self.client = Client()
self.url = "/api/tournaments/"
self.url = "/api/tournaments/"
self.username = str(uuid.uuid4())
self.password = str(uuid.uuid4())
self.username = str(uuid.uuid4())
self.password = str(uuid.uuid4())
self.nb_players_by_game = 2
self.nb_players = 8
self.nb_players_by_game = 2
self.nb_players = 8
user: User = User.objects.create_user(username=self.username, password=self.password)
self.client.login(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)
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_data: dict = json.loads(response.content)
self.assertDictContainsSubset({"name": ""}, response_data)
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_data: dict = json.loads(response.content)
self.assertDictContainsSubset({"name": ""}, response_data)
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_data = json.loads(response.content)
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):
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)
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_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_data = json.loads(response.content)
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):
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)
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):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 5, "nb_players": 3})
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.']})
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_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.']})

View File

@ -13,44 +13,44 @@ from .serializers import TournamentSerializer
# Create your views here.
class TournamentViewSet(viewsets.ModelViewSet):
queryset = TournamentModel.objects.all
serializer_class = TournamentSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
queryset = TournamentModel.objects.all
serializer_class = TournamentSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def perform_create(self, serializer: TournamentSerializer):
def perform_create(self, serializer: TournamentSerializer):
nb_players = serializer.validated_data["nb_players"]
nb_players_by_game = serializer.validated_data["nb_players_by_game"]
level = 1
number: int = nb_players
while (number != nb_players_by_game):
number = number // 2 + (number % 2)
level += 1
nb_players = serializer.validated_data["nb_players"]
nb_players_by_game = serializer.validated_data["nb_players_by_game"]
level = 1
number: int = nb_players
while (number != nb_players_by_game):
number = number // 2 + (number % 2)
level += 1
tournament = serializer.save(level = level)
return Response(self.serializer_class(tournament).data, status=status.HTTP_201_CREATED)
def list(self, request: HttpRequest, state: str = ""):
query: QuerySet
match state:
case "started":
query = TournamentModel.objects.filter(started=True, finished=False)
case "finished":
query = TournamentModel.objects.filter(finished=True)
case "waiting":
query = TournamentModel.objects.filter(started=False, finished=False)
case _:
query = TournamentModel.objects.all()
serializer = TournamentSerializer(query, many=True)
return Response(serializer.data)
tournament = serializer.save(level = level)
return Response(self.serializer_class(tournament).data, status=status.HTTP_201_CREATED)
def list(self, request: HttpRequest, state: str = ""):
query: QuerySet
match state:
case "started":
query = TournamentModel.objects.filter(started=True, finished=False)
case "finished":
query = TournamentModel.objects.filter(finished=True)
case "waiting":
query = TournamentModel.objects.filter(started=False, finished=False)
case _:
query = TournamentModel.objects.all()
serializer = TournamentSerializer(query, many=True)
return Response(serializer.data)
def retrieve(self, request: HttpRequest, pk):
def retrieve(self, request: HttpRequest, pk):
if (not TournamentModel.objects.filter(pk=pk).exists()):
return Response({"detail": "Tournament not found."}, status=status.HTTP_404_NOT_FOUND)
tournament = TournamentModel.objects.get(pk=pk)
if (not TournamentModel.objects.filter(pk=pk).exists()):
return Response({"detail": "Tournament not found."}, status=status.HTTP_404_NOT_FOUND)
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
class AbstractRoom:
def __init__(self, room_manager):
self._member_list: [AbstractRoomMember] = []
self.room_manager = room_manager
def __init__(self, room_manager):
self._member_list: [AbstractRoomMember] = []
self.room_manager = room_manager
def broadcast(self, detail: str, data: dict = {}):
for member in self._member_list:
member: AbstractRoomMember
member.send(detail, data)
def clear(self):
self._member_list.clear()
def broadcast(self, detail: str, data: dict = {}):
for member in self._member_list:
member: AbstractRoomMember
member.send(detail, data)
def clear(self):
self._member_list.clear()
def get_member_by_socket(self, socket: WebsocketConsumer):
for member in self._member_list:
member: AbstractRoomMember
if (member.socket is socket):
return member
return None
def get_member_by_socket(self, socket: WebsocketConsumer):
for member in self._member_list:
member: AbstractRoomMember
if (member.socket is socket):
return member
return None
def get_member_by_user_id(self, user_id: int):
for member in self._member_list:
member: AbstractRoomMember
if (member.user_id == user_id):
return member
return None
def get_member_by_user_id(self, user_id: int):
for member in self._member_list:
member: AbstractRoomMember
if (member.user_id == user_id):
return member
return None
def append(self, member: AbstractRoomMember):
self._member_list.append(member)
member.accept()
def append(self, member: AbstractRoomMember):
self._member_list.append(member)
member.accept()
def remove(self, member: AbstractRoomMember, code: int = 1000):
self._member_list.remove(member)
member.disconnect(code)
def remove(self, member: AbstractRoomMember, code: int = 1000):
self._member_list.remove(member)
member.disconnect(code)
def empty(self):
for _ in self._member_list:
return False
return True
def empty(self):
for _ in self._member_list:
return False
return True
def get_users_id(self):
return [member.user_id for member in self._member_list]
def get_users_id(self):
return [member.user_id for member in self._member_list]
def __len__(self):
return len(self._member_list)
def __len__(self):
return len(self._member_list)

View File

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

View File

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

View File

@ -22,13 +22,13 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings')
application = ProtocolTypeRouter({
'http':get_asgi_application(),
'websocket':AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns +
matchmaking.routing.websocket_urlpatterns +
tournament.routing.websocket_urlpatterns +
games.routing.websocket_urlpatterns
)
)
'websocket':AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns +
matchmaking.routing.websocket_urlpatterns +
tournament.routing.websocket_urlpatterns +
games.routing.websocket_urlpatterns
)
)
})

View File

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