76 Commits

Author SHA1 Message Date
ae20be25fb add: matchmaking 2023-12-12 12:06:13 +01:00
ad6cfdf08a Merge branch 'server' into feat/matchmaking 2023-12-12 10:01:12 +01:00
08ce682980 rename id to user_id 2023-12-12 09:53:28 +01:00
982130a02f 🤓 2023-12-11 15:59:15 +01:00
326724930a Merge branch 'server' into feat/matchmaking 2023-12-11 15:55:12 +01:00
85787760b9 fix: back button is back 2023-12-11 15:54:56 +01:00
fb1b71ade6 Merge branch 'server' into feat/matchmaking 2023-12-11 15:24:12 +01:00
2ccfc5464a fix: fix: + ratio 2023-12-11 15:23:42 +01:00
d8a279f4d8 fix: profiles list display new profiles 2023-12-11 14:36:36 +01:00
336257d1d0 init view 2023-12-11 13:40:01 +01:00
f66b3883c1 init app 2023-12-11 13:39:53 +01:00
9c1dd30db7 add: css to me 2023-12-11 13:25:00 +01:00
079be0bb46 delete accounts auto logout 2023-12-11 12:43:49 +01:00
df436e0b88 fix: user can change avatar 2023-12-11 12:43:36 +01:00
c3c83b3168 add: profiles and profile page 2023-12-09 21:51:32 +01:00
dd19e15e7d fix: add return to profiles.all() 2023-12-09 15:13:40 +01:00
0edcd97f94 add: profiles.all() 2023-12-08 17:36:41 +01:00
54afa8aae5 move image path 2023-12-06 16:48:21 +01:00
2ce1356412 remove: bozo print 2023-12-06 15:33:40 +01:00
d1c75f7033 clean: remove bozo commnet 2023-12-06 15:33:18 +01:00
3833c647aa ignore avatars 2023-12-06 15:33:04 +01:00
32a8cbfda6 clean: rm trash 2023-12-06 15:20:37 +01:00
9b6c5547f0 add: profile avatar 2023-12-06 15:19:41 +01:00
910644a804 fix, add auto login when register 2023-12-04 12:32:15 +01:00
25f315c24f add: delete and edit accounts page 2023-12-01 01:29:56 +01:00
c2b6dbb989 api: add: edit accounts 2023-11-30 14:03:38 +01:00
086c20bddc change delete method in tests 2023-11-30 13:41:31 +01:00
a9cdde963d update url in tester 2023-11-30 13:40:04 +01:00
3403577c3e update test url 2023-11-30 13:39:22 +01:00
5d8005df44 remove settilte on child class 2023-11-30 13:05:46 +01:00
65a027014b chore: rename classs 2023-11-29 22:51:57 +01:00
bf3393e9a9 rename view 2023-11-29 20:56:32 +01:00
9ae0dd0e28 bozo 2023-11-29 20:03:48 +01:00
d64e62101a clean: remove bozo import 2023-11-29 20:00:22 +01:00
84a5a592ca add: Abstract class to simplifie the code,
AbstractRedirectView,
AbstractAuthentificateView
AbstractUnAuthentificateView
2023-11-29 19:55:44 +01:00
a6666b889f use replace ws by wss 2023-11-29 19:20:12 +01:00
9947ea37e2 Merge branch 'Chatte' into feat/accounts 2023-11-29 18:36:08 +01:00
7e34f883aa fix: click on loggin protected do not change
history
2023-11-29 17:04:00 +01:00
07d06253ba opti: do not execute postInit if getHtml is null 2023-11-29 16:19:17 +01:00
d5e692449b fix: move code to not render html when not needed 2023-11-29 16:10:55 +01:00
6dc0293455 fix: connexion 2023-11-29 16:05:49 +01:00
b12c03074a Chat advance 2023-11-27 23:31:31 +01:00
8bce7d33ca The project advance 2023-11-27 21:59:41 +01:00
b5b54a98ba fix: de golmon 2023-11-27 19:16:30 +01:00
25721bdda8 add: home 2023-11-27 19:15:15 +01:00
27044e9bdb add register button 2023-11-27 19:13:47 +01:00
3f3ab52a09 use post init to onclick 2023-11-27 19:11:15 +01:00
ffbfe2ddd0 merge Ukrainia and Russia 2023-11-27 16:45:32 +01:00
9c59401cf2 Merge Antisémite and Sionisme plz 2023-11-27 15:52:32 +01:00
d490208ffa Merge branch 'Chatte' into feat/accounts 2023-11-27 15:48:11 +01:00
2a468bcb82 forget push file 2023-11-27 15:47:33 +01:00
7b6a8ba57b Minor Edit and clear one file 2023-11-27 15:47:00 +01:00
27e75c4497 login style 2023-11-27 15:32:33 +01:00
a965adfdce add: register page 2023-11-27 15:32:17 +01:00
c16d281892 Global Chat 2023-11-27 15:10:06 +01:00
56b6f0e138 Pussy 2023-11-27 14:35:11 +01:00
8202a8b88d Add PostInit in index.js/AbstractViews.js 2023-11-27 14:31:47 +01:00
6a4e161d71 add: db instruction readme 2023-11-27 14:09:18 +01:00
b98371cf70 display error, and add style 2023-11-27 13:55:05 +01:00
b95bed8894 Alors ce commit sert à CRAMPTÉ 2023-11-27 13:23:54 +01:00
ea42d10ddf init login 2023-11-23 17:26:09 +01:00
267eeab896 Update README.md 2023-11-22 14:23:36 +01:00
4a5b44f0e2 urls use /api/ and / to frontend 2023-11-21 19:20:59 +01:00
45b782e94c add frontend 2023-11-21 19:15:16 +01:00
36d97eb58c clean 2023-11-13 13:31:56 +01:00
9aa724e19e fix: use the right post data 2023-11-13 13:22:10 +01:00
b9728dcb06 profile: rename multiple var and change to use
django rest frameword
2023-11-12 00:03:08 +01:00
a7d9471d59 core: use rest_framework in accounts 2023-11-11 19:50:14 +01:00
eb8789aa1d install to dependency 2023-11-08 14:51:48 +01:00
9d8476ebb8 fix: import logout in login.py 2023-11-03 21:33:48 +01:00
16c47b47cf doc: switch to django branch 2023-10-31 21:38:34 +01:00
827743bb96 doc: add installation man 2023-10-31 21:26:17 +01:00
a0c0d813b6 register: use ModelForm, and print all errors 2023-10-31 21:14:24 +01:00
54cc1b1705 profiles: init 2023-10-29 21:09:38 +01:00
246213c5b8 core: add anotation type 2023-10-29 21:00:18 +01:00
e253631f09 core: reformat file tree 2023-10-25 16:50:05 +02:00
115 changed files with 1785 additions and 481 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.env
*.pyc
db.sqlite3
**/migrations/**
/profiles/static/avatars/*
!/profiles/static/avatars/default

31
README.md Normal file
View File

@ -0,0 +1,31 @@
# BACKEND
## Installation
- Clone the project:
``` bash
git clone https://git.chauvet.pro/michel/ft_transcendence
cd ft_transcendence
git switch server
```
- Create python virtual environnement.
``` bash
python3 -m venv .env
```
- Source the environnement.
``` bash
source .env/bin/activate
```
- Install the requirements
``` bash
pip install -r requirements.txt
```
- Setup database
```
python manage.py runserver makemigrations profiles games
python manage.py migrate profiles games
```
- Start the developpement server
```
python manage.py runserver 0.0.0.0:8000
```

View File

@ -0,0 +1,12 @@
from rest_framework.serializers import Serializer, CharField
from django.contrib.auth import authenticate
from django.core.exceptions import ValidationError
class LoginSerializer(Serializer):
username = CharField()
password = CharField()
def get_user(self, data):
user = authenticate(username=data['username'], password=data['password'])
return user

View File

@ -0,0 +1,12 @@
from rest_framework.serializers import ModelSerializer
from django.contrib.auth.models import User
class RegisterSerialiser(ModelSerializer):
class Meta:
model = User
fields = ['username', 'password']
def create(self, data):
user_obj = User.objects.create_user(username=data['username'], password=data['password'])
user_obj.save()
return user_obj

View File

@ -1,4 +1,5 @@
from .register import * from .register import *
from .login import * from .login import *
from .change_password import * from .logout import *
from .edit import *
from .delete import * from .delete import *

37
accounts/tests/delete.py Normal file
View File

@ -0,0 +1,37 @@
from django.test import TestCase
# Create your tests here.
from django.test.client import Client
from django.http import HttpResponse
from django.contrib.auth.models import User
import uuid
class DeleteTest(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/accounts/delete"
self.username: str = str(uuid.uuid4())
self.password: str = str(uuid.uuid4())
user: User = User.objects.create_user(username=self.username, password=self.password)
self.client.login(username=self.username, password=self.password)
def test_normal_delete(self):
response: HttpResponse = self.client.delete(self.url, {"password": self.password}, content_type='application/json')
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, '"user deleted"')
def test_wrong_pass(self):
response: HttpResponse = self.client.delete(self.url, {"password": "cacaman a frapper"}, content_type='application/json')
errors: dict = eval(response.content)
self.assertDictEqual(errors, {"password": ["Password wrong."]})
def test_no_logged(self):
self.client.logout()
response: HttpResponse = self.client.delete(self.url, {"password": self.password}, content_type='application/json')
errors: dict = eval(response.content)
self.assertDictEqual(errors, {"detail":"Authentication credentials were not provided."})

49
accounts/tests/edit.py Normal file
View File

@ -0,0 +1,49 @@
from django.test import TestCase
# Create your tests here.
from django.test.client import Client
from django.http import HttpResponse
from django.contrib.auth.models import User
import uuid
class EditTest(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/accounts/edit"
self.username: str = str(uuid.uuid4())
self.password: str = str(uuid.uuid4())
self.new_password: str = str(uuid.uuid4())
User.objects.create_user(username = self.username, password = self.password)
def test_normal(self):
self.client.login(username = self.username, password = self.password)
response: HttpResponse = self.client.patch(self.url, {"current_password": self.password, "new_password": self.new_password, "username": "bozo"}, content_type='application/json')
response_text: str = response.content.decode('utf-8')
self.assertEqual(response_text, '"data has been alterate"')
def test_invalid_current_password(self):
self.client.login(username = self.username, password = self.password)
response: HttpResponse = self.client.patch(self.url, {"current_password": "bozo", "new_password": self.new_password, "username": "bozo"}, content_type='application/json')
errors: dict = eval(response.content)
self.assertDictEqual(errors, {"current_password":["Password is wrong."]})
def test_invalid_new_username_blank(self):
self.client.login(username = self.username, password = self.password)
response: HttpResponse = self.client.patch(self.url, {"current_password": self.password, "username": " "}, content_type='application/json')
errors: dict = eval(response.content)
self.assertDictEqual(errors, {'username': ['This field may not be blank.']})
def test_invalid_new_username_char(self):
self.client.login(username = self.username, password = self.password)
response: HttpResponse = self.client.patch(self.url, {"current_password": self.password, "username": "*&"}, content_type='application/json')
errors: dict = eval(response.content)
self.assertDictEqual(errors, {'username': ['Enter a valid username. This value may contain only letters, numbers, and @/./+/-/_ characters.']})
def test_nologged(self):
response: HttpResponse = self.client.patch(self.url, {"current_password": self.password, "new_password": self.new_password}, content_type='application/json')
errors: dict = eval(response.content)
self.assertDictEqual(errors, {'detail': 'Authentication credentials were not provided.'})

53
accounts/tests/login.py Normal file
View File

@ -0,0 +1,53 @@
from django.test import TestCase
# Create your tests here.
from django.test.client import Client
from django.contrib.auth.models import User
from django.http import HttpResponse
import uuid
class LoginTest(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/accounts/login"
self.username: str = str(uuid.uuid4())
self.password: str = str(uuid.uuid4())
User.objects.create_user(username=self.username, password=self.password)
def test_normal_login(self):
response: HttpResponse = self.client.post(self.url, {'username': self.username, 'password': self.password})
response_text = response.content.decode('utf-8')
#self.assertEqual(response_text, 'user connected')
def test_invalid_username(self):
response: HttpResponse = self.client.post(self.url, {"username": self.password, "password": self.password})
errors: dict = eval(response.content)
errors_expected: dict = {'user': ['Username or password wrong.']}
self.assertEqual(errors, errors_expected)
def test_invalid_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.username})
errors: dict = eval(response.content)
errors_expected: dict = {'user': ['Username or password wrong.']}
self.assertEqual(errors, errors_expected)
def test_invalid_no_username(self):
response: HttpResponse = self.client.post(self.url, {"password": self.password})
errors: dict = eval(response.content)
errors_expected: dict = {'username': ['This field is required.']}
self.assertEqual(errors, errors_expected)
def test_invalid_no_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
errors: dict = eval(response.content)
errors_expected: dict = {'password': ['This field is required.']}
self.assertEqual(errors, errors_expected)
def test_invalid_no_password_no_username(self):
response: HttpResponse = self.client.post(self.url, {})
errors: dict = eval(response.content)
errors_expected: dict = {'username': ['This field is required.'], 'password': ['This field is required.']}
self.assertEqual(errors, errors_expected)

17
accounts/tests/logout.py Normal file
View File

@ -0,0 +1,17 @@
from django.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User
from django.contrib.auth import login
class LoginTest(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/accounts/logout"
self.client.login()
def test_normal_logout(self):
response: HttpResponse = self.client.post(self.url)
self.assertNotIn('_auth_user_id', self.client.session)

View File

@ -0,0 +1,52 @@
from django.test import TestCase
# Create your tests here.
from rest_framework import status
from django.test.client import Client
from django.contrib.auth.models import User
from django.http import HttpResponse
import uuid
class RegisterTest(TestCase):
def setUp(self):
self.client = Client()
self.url: str = "/api/accounts/register"
self.username: str = str(uuid.uuid4())
self.password: str = str(uuid.uuid4())
def test_normal_register(self):
response: HttpResponse = self.client.post(self.url, {'username': self.username, 'password': self.password})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_incomplet_form_no_username_no_password(self):
response: HttpResponse = self.client.post(self.url)
errors: dict = eval(response.content)
errors_expected: dict = {'username': ['This field is required.'], 'password': ['This field is required.']}
self.assertEqual(errors, errors_expected)
def test_incomplet_form_no_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
errors: dict = eval(response.content)
errors_expected: dict = {'password': ['This field is required.']}
self.assertEqual(errors, errors_expected)
def test_incomplet_form_no_username(self):
response: HttpResponse = self.client.post(self.url, {"password": self.password})
errors: dict = eval(response.content)
errors_expected: dict = {}
self.assertEqual(errors, errors_expected)
def test_incomplet_form_no_username(self):
response: HttpResponse = self.client.post(self.url, {"password": self.password})
errors: dict = eval(response.content)
errors_expected: dict = {'username': ['This field is required.']}
self.assertEqual(errors, errors_expected)
def test_already_registered(self):
User(username=self.username, password=self.password).save()
response: HttpResponse = self.client.post(self.url, {'username': self.username, 'password': self.password})
errors: dict = eval(response.content)
errors_expected: dict = {'username': ['A user with that username already exists.']}
self.assertEqual(errors, errors_expected)

13
accounts/urls.py Normal file
View File

@ -0,0 +1,13 @@
from django.urls import path
from .views import register, login, logout, delete, edit, logged
urlpatterns = [
path("register", register.RegisterView.as_view(), name="register"),
path("login", login.LoginView.as_view(), name="login"),
path("logout", logout.LogoutView.as_view(), name="logout"),
path("logged", logged.LoggedView.as_view(), name="logged"),
path("delete", delete.DeleteView.as_view(), name="delete"),
path("edit", edit.EditView.as_view(), name="change_password")
]

21
accounts/views/delete.py Normal file
View File

@ -0,0 +1,21 @@
from rest_framework.views import APIView
from rest_framework import permissions, status
from rest_framework.response import Response
from django.contrib.auth import logout
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
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)

45
accounts/views/edit.py Normal file
View File

@ -0,0 +1,45 @@
from rest_framework.views import APIView
from rest_framework.response import Response
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.contrib.auth.models import User
import re
class EditView(APIView):
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest):
return Response({"username": request.user.username, "id": request.user.pk})
def patch(self, request: HttpRequest):
data: dict = request.data
current_password: str = data.get("current_password")
if (current_password is None):
return Response({"current_password": ["This field may not be blank."]})
user_object = request.user
if (user_object.check_password(current_password) == False):
return Response({"current_password": ["Password is wrong."]})
new_username = data.get("username", user_object.username)
if (new_username != user_object.username):
if (User.objects.filter(username=new_username).exists()):
return Response({"username": ["A user with that username already exists."]})
if (set(new_username) == {' '}):
return Response({"username": ["This field may not be blank."]})
if (re.search('^([a-z]||\@||\+||\-||\_)+$', new_username) is None):
return Response({"username":["Enter a valid username. This value may contain only letters, numbers, and @/./+/-/_ characters."]})
new_password: str = data.get("password")
if (new_password is not None):
user_object.set_password(new_password)
user_object.save()
return Response("data has been alterate")

18
accounts/views/logged.py Normal file
View File

@ -0,0 +1,18 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from django.http import HttpRequest
from django.contrib.auth import login
from rest_framework.authentication import SessionAuthentication
from ..serializers.login import LoginSerializer
class LoggedView(APIView):
permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest):
if (request.user.is_authenticated):
return Response({'id': request.user.pk}, status=status.HTTP_200_OK)
return Response('false', status=status.HTTP_200_OK)

23
accounts/views/login.py Normal file
View File

@ -0,0 +1,23 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from django.http import HttpRequest
from django.contrib.auth import login
from rest_framework.authentication import SessionAuthentication
from ..serializers.login import LoginSerializer
class LoginView(APIView):
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({'user': ['Username or password wrong.']}, status.HTTP_200_OK)
login(request, user)
return Response({'id': user.pk}, status=status.HTTP_200_OK)

13
accounts/views/logout.py Normal file
View File

@ -0,0 +1,13 @@
from rest_framework.views import APIView
from django.contrib.auth import logout
from rest_framework import permissions, status
from rest_framework.response import Response
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)

View File

@ -0,0 +1,18 @@
from rest_framework import permissions, status
from ..serializers.register import RegisterSerialiser
from rest_framework.views import APIView
from rest_framework.response import Response
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)

6
chat/apps.py Normal file
View File

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

36
chat/consumers.py Normal file
View File

@ -0,0 +1,36 @@
import json
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_group_name = 'test'
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type':'chat_message',
'message':message
}
)
def chat_message(self, event):
message = event['message']
self.send(text_data=json.dumps({
'type':'chat',
'username':self.scope["user"].username,
'message':message
}))

6
chat/routing.py Normal file
View File

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

4
django/.gitignore vendored
View File

@ -1,4 +0,0 @@
.env
*.pyc
db.sqlite3
**/migrations/*

View File

@ -1,3 +0,0 @@
asgiref==3.7.2
Django==4.2.6
sqlparse==0.4.4

View File

@ -1,7 +0,0 @@
from django import forms
from ..settings import *
class ChangePasswordForm(forms.Form):
username = forms.CharField(label="username", max_length=USERNAME_MAX_SIZE, min_length=USERNAME_MIN_SIZE, required=True)
current_password = forms.CharField(label="current_password", max_length=PASSWORD_MAX_SIZE, min_length=PASSWORD_MIN_SIZE, required=True)
new_password = forms.CharField(label="new_password", max_length=PASSWORD_MAX_SIZE, min_length=PASSWORD_MIN_SIZE, required=True)

View File

@ -1,6 +0,0 @@
from django import forms
from ..settings import *
class DeleteForm(forms.Form):
username = forms.CharField(label="username", max_length=USERNAME_MAX_SIZE, min_length=USERNAME_MIN_SIZE, required=True)
password = forms.CharField(label="password", max_length=PASSWORD_MAX_SIZE, min_length=PASSWORD_MIN_SIZE, required=True)

View File

@ -1,6 +0,0 @@
from django import forms
from ..settings import *
class LoginForm(forms.Form):
username = forms.CharField(label="username", max_length=USERNAME_MAX_SIZE, min_length=USERNAME_MIN_SIZE, required=True)
password = forms.CharField(label="password", max_length=PASSWORD_MAX_SIZE, min_length=PASSWORD_MIN_SIZE, required=True)

View File

@ -1,6 +0,0 @@
from django import forms
from ..settings import *
class RegisterForm(forms.Form):
username = forms.CharField(label="username", max_length=USERNAME_MAX_SIZE, min_length=USERNAME_MIN_SIZE, required=True)
password = forms.CharField(label="password", max_length=PASSWORD_MAX_SIZE, min_length=PASSWORD_MIN_SIZE, required=True)

View File

@ -1,4 +0,0 @@
PASSWORD_MIN_SIZE = 3
PASSWORD_MAX_SIZE = 128
USERNAME_MIN_SIZE = 3
USERNAME_MAX_SIZE = 40

View File

@ -1,9 +0,0 @@
INVALID_USERNAME: str = "error: username invalid"
INVALID_PASSWORD: str = "error: password invalid"
INVALID_USERNAME_PASSWORD: str = "error: username or password invalid"
USERNAME_ALREADY_USED: str = "error: username already used"
USER_ADDED: str = "ok: user added"
USER_DELETED: str = "ok: account has been deleted"
USER_VALID: str = "ok: account valid"
PASSWORD_UPDATED: str = "ok: password has been updated"

View File

@ -1,9 +0,0 @@
<html>
<form method='post'>
{% csrf_token %}
<input type="text" name="username" placeholder="username">
<input type="text" name="current_password" placeholder="current_password">
<input type="text" name="new_password" placeholder="new_password">
<input type='submit'>
</form>
</html>

View File

@ -1,7 +0,0 @@
<html>
<form method='post'>
{% csrf_token %}
{{ form }}
<input type='submit'>
</form>
</html>

View File

@ -1,7 +0,0 @@
<html>
<form method='post'>
{% csrf_token %}
{{ form }}
<input type='submit'>
</form>
</html>

View File

@ -1,7 +0,0 @@
<html>
<form method='post'>
{% csrf_token %}
{{ form }}
<input type='submit'>
</form>
</html>

View File

@ -1,76 +0,0 @@
from django.test import TestCase
# Create your tests here.
from django.test.client import Client
from django.http import HttpResponse
import uuid
from ..status_code import *
from ..settings import *
class ChangePasswordTest(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/accounts/change_password"
self.username: str = str(uuid.uuid4())[:USERNAME_MAX_SIZE]
self.password: str = str(uuid.uuid4())[:PASSWORD_MAX_SIZE]
self.new_password: str = str(uuid.uuid4())[:PASSWORD_MAX_SIZE]
self.client.post("/api/accounts/register", {"username": self.username, "password": self.password})
def test_normal_login(self):
response: HttpResponse = self.client.post("/api/accounts/login", {"username": self.username, "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, USER_VALID)
def test_invalid_username(self):
response: HttpResponse = self.client.post(self.url, {"username": self.password, "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_username(self):
response: HttpResponse = self.client.post(self.url, {"password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_password_no_username(self):
response: HttpResponse = self.client.post(self.url, {})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_no_new_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "current_password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_new_password_to_short(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "current_password": self.password, "new_password": "a" * (PASSWORD_MIN_SIZE - (PASSWORD_MIN_SIZE > 0))})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_new_password_to_long(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "current_password": self.password, "new_password": "a" * (PASSWORD_MAX_SIZE + 1)})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_normal_change_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "current_password": self.password, "new_password": self.new_password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, PASSWORD_UPDATED)

View File

@ -1,55 +0,0 @@
from django.test import TestCase
# Create your tests here.
from django.test.client import Client
from django.http import HttpResponse
import uuid
from ..status_code import *
from ..settings import *
class DeleteTest(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/accounts/delete"
self.username: str = str(uuid.uuid4())[:USERNAME_MAX_SIZE]
self.password: str = str(uuid.uuid4())[:PASSWORD_MAX_SIZE]
self.client.post("/api/accounts/register", {"username": self.username, "password": self.password})
def test_invalid_username(self):
response: HttpResponse = self.client.post(self.url, {"username": self.password, "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_username(self):
response: HttpResponse = self.client.post(self.url, {"password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_password_no_username(self):
response: HttpResponse = self.client.post(self.url, {})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_normal_delete(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, USER_DELETED)

View File

@ -1,50 +0,0 @@
from django.test import TestCase
# Create your tests here.
from django.test.client import Client
from django.http import HttpResponse
import uuid
from ..status_code import *
from ..settings import *
class LoginTest(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/accounts/login"
self.username: str = str(uuid.uuid4())[:USERNAME_MAX_SIZE]
self.password: str = str(uuid.uuid4())[:PASSWORD_MAX_SIZE]
self.client.post("/api/accounts/register", {"username": self.username, "password": self.password})
def test_normal_login(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, USER_VALID)
def test_invalid_username(self):
response: HttpResponse = self.client.post(self.url, {"username": self.password, "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_username(self):
response: HttpResponse = self.client.post(self.url, {"password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_invalid_no_password_no_username(self):
response: HttpResponse = self.client.post(self.url, {})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)

View File

@ -1,69 +0,0 @@
from django.test import TestCase
# Create your tests here.
from django.test.client import Client
from django.http import HttpResponse
import uuid
from ..status_code import *
from ..settings import *
class RegisterTest(TestCase):
def setUp(self):
self.client = Client()
self.url: str = "/api/accounts/register"
self.username: str = str(uuid.uuid4())[:USERNAME_MAX_SIZE]
self.password: str = str(uuid.uuid4())[:PASSWORD_MAX_SIZE]
def test_incomplet_form_no_username_no_password(self):
response: HttpResponse = self.client.post(self.url)
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_incomplet_form_no_password(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_incomplet_form_no_username(self):
response: HttpResponse = self.client.post(self.url, {"password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, "error: username invalid")
def test_incomplet_form_no_username(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_normal_register(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, USER_ADDED)
def test_username_too_short(self):
response: HttpResponse = self.client.post(self.url, {"username": "a" * (USERNAME_MIN_SIZE - (USERNAME_MIN_SIZE > 0)), "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_username_too_long(self):
response: HttpResponse = self.client.post(self.url, {"username": "a" * (USERNAME_MAX_SIZE + 1), "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_password_too_short(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": "a" * (PASSWORD_MIN_SIZE - (PASSWORD_MIN_SIZE > 0))})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_password_too_long(self):
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": "a" * (PASSWORD_MAX_SIZE + 1)})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, INVALID_USERNAME_PASSWORD)
def test_already_registered(self):
self.client.post(self.url, {"username": self.username, "password": self.password})
response: HttpResponse = self.client.post(self.url, {"username": self.username, "password": self.password})
response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, USERNAME_ALREADY_USED)

View File

@ -1,10 +0,0 @@
from django.urls import path
from .views import login, register, delete, change_password
urlpatterns = [
path("login", login.LoginView.as_view(), name="login"),
path("register", register.RegisterView.as_view(), name="register"),
path("delete", delete.DeleteView.as_view(), name="delete"),
path("change_password", change_password.ChangePasswordView.as_view(), name="change_password"),
]

View File

@ -1,36 +0,0 @@
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse, HttpRequest
from django.contrib.auth.models import User
from django.db.models.query import QuerySet
from ..status_code import *
from ..settings import *
from ..forms.change_password import ChangePasswordForm
class ChangePasswordView(View):
def get(self, request: HttpRequest):
return render(request, "change_password.html")
def post(self, request: HttpRequest):
form: ChangePasswordForm = ChangePasswordForm(request.POST)
if not form.is_valid():
return HttpResponse(INVALID_USERNAME_PASSWORD)
username: str = form.cleaned_data['username']
current_password: str = form.cleaned_data['current_password']
new_password: str = form.cleaned_data['new_password']
query: QuerySet = User.objects.filter(username=username)
if (not query.exists()):
return HttpResponse(INVALID_USERNAME_PASSWORD)
user: User = User.objects.get(username=username)
if (not user.check_password(current_password)):
return HttpResponse(INVALID_USERNAME_PASSWORD)
user.set_password(new_password)
user.save()
return HttpResponse(PASSWORD_UPDATED)

View File

@ -1,35 +0,0 @@
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse, HttpRequest
from django.contrib.auth.models import User
from django.db.models.query import QuerySet
from ..status_code import *
from ..settings import *
from ..forms.delete import DeleteForm
class DeleteView(View):
def get(self, request: HttpRequest):
return render(request, "delete.html", {"form": DeleteForm})
def post(self, request: HttpRequest):
form: DeleteForm = DeleteForm(request.POST)
if (not form.is_valid()):
return HttpResponse(INVALID_USERNAME_PASSWORD)
username: str = form.cleaned_data['username']
password: str = form.cleaned_data['password']
query: QuerySet = User.objects.filter(username=username)
if (not query.exists()):
return HttpResponse(INVALID_USERNAME_PASSWORD)
user: User = User.objects.get(username=username)
if (not user.check_password(password)):
return HttpResponse(INVALID_USERNAME_PASSWORD)
user.delete()
return HttpResponse(USER_DELETED)

View File

@ -1,31 +0,0 @@
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse
from django.contrib.auth.models import User
from django.db.models.query import QuerySet
from ..status_code import *
from ..settings import *
from ..forms.login import LoginForm
class LoginView(View):
def get(self, request):
return render(request, "login.html", {"form": LoginForm})
def post(self, request):
form: LoginForm = LoginForm(request.POST)
if not form.is_valid():
return HttpResponse(INVALID_USERNAME_PASSWORD)
username: str = form.cleaned_data["username"]
password: str = form.cleaned_data["password"]
query: QuerySet = User.objects.filter(username=username)
if (not query.exists()):
return HttpResponse(INVALID_USERNAME_PASSWORD)
user: User = User.objects.get(username=username)
if (not user.check_password(password)):
return HttpResponse(INVALID_USERNAME_PASSWORD)
return HttpResponse(USER_VALID)

View File

@ -1,29 +0,0 @@
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse, HttpRequest
from django.contrib.auth.models import User
from django.db.models.query import QuerySet
from ..status_code import *
from ..settings import *
from ..forms.register import RegisterForm
class RegisterView(View):
def get(self, request: HttpRequest):
return render(request, "register.html", {"form": RegisterForm})
def post(self, request: HttpRequest):
form: RegisterForm = RegisterForm(request.POST)
if not form.is_valid():
return HttpResponse(INVALID_USERNAME_PASSWORD)
username: str = form.cleaned_data["username"]
password: str = form.cleaned_data["password"]
if User.objects.filter(username=username).exists():
return HttpResponse(USERNAME_ALREADY_USED)
user = User.objects.create_user(username, password=password)
user.save()
return HttpResponse(USER_ADDED)

View File

@ -1,7 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path("<str:uuid>", views.ProfilePage.as_view(), name="profile_page"),
]

3
frontend/admin.py Normal file
View File

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

6
frontend/apps.py Normal file
View File

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

View File

@ -0,0 +1,12 @@
#app .form {
background-color: red;
width: 300px;
height: 300px;
display: grid;
grid-template-columns: repeat(1, 1fr);
grid-gap: 10px;
margin-left: auto;
margin-right: auto;
margin-top: 90px;
border: 15px black solid;
}

View File

@ -0,0 +1,12 @@
#app .form {
background-color: red;
width: 300px;
height: 300px;
display: grid;
grid-template-columns: repeat(1, 1fr);
grid-gap: 10px;
margin-left: auto;
margin-right: auto;
margin-top: 90px;
border: 15px black solid;
}

View File

@ -0,0 +1,9 @@
body {
margin: 10;
font-family: 'Quicksand', sans-serif;
font-size: 18px;
}
a {
color: #009579;
}

View File

@ -0,0 +1,19 @@
#app .account
{
background-color: red;
}
#app .account, #app .profile
{
width: 60%;
display: flex;
margin-left: auto;
margin-right: auto;
flex-direction: column;
flex-wrap: wrap;
}
#app .profile
{
background-color: green;
}

View File

@ -0,0 +1,11 @@
#app #avatar
{
height: 100px;
width: 100px;
}
#app #username
{
height: 100px;
width: 100px;
}

View File

@ -0,0 +1,11 @@
#app .item img
{
height: 100px;
width: 100px;
}
#app .item a
{
height: 100px;
width: 100px;
}

View File

@ -0,0 +1,65 @@
class Account
{
constructor (client)
{
this.client = client;
}
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")
{
this._logged = true;
return null;
}
return response_data
}
async delete(password)
{
let response = await this.client._delete("/api/accounts/delete", {password: password});
let response_data = await response.json();
if (JSON.stringify(response_data) == JSON.stringify({'detail': 'Authentication credentials were not provided.'}))
{
this.client._logged = false;
return null;
}
console.log(response_data)
if (response_data == "user deleted")
this.client._logged = false;
return response_data;
}
async get()
{
let response = await this.client._get("/api/accounts/edit");
let response_data = await response.json();
if (JSON.stringify(response_data) == JSON.stringify({'detail': 'Authentication credentials were not provided.'}))
{
this.client._logged = false;
return null;
}
return response_data;
}
async update(data, password)
{
data.current_password = password;
let response = await this.client._patch_json("/api/accounts/edit", data);
let response_data = await response.json();
if (JSON.stringify(response_data) == JSON.stringify({'detail': 'Authentication credentials were not provided.'}))
{
this.client._logged = false;
return null;
}
return response_data;
}
}
export { Account }

View File

@ -0,0 +1,127 @@
import { Account } from "./account.js";
import { MatchMaking } from "./matchmaking.js";
import { Profile } from "./profile.js";
import { Profiles } from "./profiles.js";
function getCookie(name)
{
let cookie = {};
document.cookie.split(';').forEach(function(el) {
let split = el.split('=');
cookie[split[0].trim()] = split.slice(1).join("=");
})
return cookie[name];
}
class Client
{
constructor(url)
{
this._url = url;
this.account = new Account(this);
this.profiles = new Profiles(this);
this.matchmaking = new MatchMaking(this);
this._logged = undefined;
}
async isAuthentificate()
{
if (this._logged == undefined)
this.logged = await this._test_logged();
return this.logged;
}
async _get(uri)
{
let response = await fetch(this._url + uri, {
method: "GET",
});
return response;
}
async _post(uri, data)
{
let response = await fetch(this._url + uri, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": getCookie("csrftoken"),
},
body: JSON.stringify(data),
});
return response;
}
async _delete(uri, data)
{
let response = await fetch(this._url + uri, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": getCookie("csrftoken"),
},
body: JSON.stringify(data),
});
return response;
}
async _patch_json(uri, data)
{
let response = await fetch(this._url + uri, {
method: "PATCH",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
return response;
}
async _patch_file(uri, file)
{
let response = await fetch(this._url + uri, {
method: "PATCH",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
body: file,
});
return response;
}
async login(username, password)
{
let response = await this._post("/api/accounts/login", {username: username, password: password})
let data = await response.json();
if (data.id != undefined)
{
this.me = new Profile(this)
await this.me.init(data.id)
this.logged = true;
return null;
}
return data;
}
async logout()
{
await this._get("/api/accounts/logout");
this.logged = false;
}
async _test_logged()
{
let response = await this._get("/api/accounts/logged");
let data = await response.json();
if (data.id !== undefined)
{
this.me = new Profile(this)
await this.me.init(data.id)
}
return data.id !== undefined;
}
}
export {Client}

View File

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

View File

@ -0,0 +1,35 @@
class Profile
{
constructor (client, username = undefined, avatar_url = undefined, user_id = undefined)
{
this.client = client;
this.username = username;
this.avatar_url = avatar_url
this.user_id = user_id
}
async init(user_id)
{
let response = await this.client._get(`/api/profiles/${user_id}`);
let response_data = await response.json();
this.user_id = user_id;
this.username = response_data.username;
this.avatar_url = response_data.avatar_url;
}
async change_avatar(form_data)
{
let response = await this.client._patch_file(`/api/profiles/${this.user_id}`, form_data);
let response_data = await response.json()
return response_data;
}
async setData (data)
{
}
}
export {Profile}

View File

@ -0,0 +1,30 @@
import { Profile } from "./profile.js";
class Profiles
{
constructor (client)
{
this.client = client
}
async all()
{
let response = await this.client._get("/api/profiles/");
let response_data = await response.json();
let profiles = []
response_data.forEach((profile) => {
profiles.push(new Profile(this.client, profile.username, profile.avatar_url, profile.user_id))
});
return profiles;
}
async getProfile(user_id)
{
let profile = new Profile(this.client);
await profile.init(user_id);
return profile;
}
}
export {Profiles}

101
frontend/static/js/index.js Normal file
View File

@ -0,0 +1,101 @@
import LoginView from "./views/accounts/LoginView.js";
import Dashboard from "./views/Dashboard.js";
import Settings from "./views/Settings.js";
import Chat from "./views/Chat.js";
import HomeView from "./views/HomeView.js";
import RegisterView from "./views/accounts/RegisterView.js";
import LogoutView from "./views/accounts/LogoutView.js";
import { Client } from "./api/client.js";
import AbstractRedirectView from "./views/AbstractRedirectView.js";
import MeView from "./views/MeView.js";
import ProfilePageView from "./views/profiles/ProfilePageView.js";
import ProfilesView from "./views/profiles/ProfilesView.js";
import MatchMakingView from "./views/MatchMakingView.js";
let client = new Client(location.protocol + "//" + location.host)
let lastView = undefined
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
const getParams = match => {
const values = match.result.slice(1);
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
return Object.fromEntries(keys.map((key, i) => {
return [key, values[i]];
}));
};
const navigateTo = async (uri) => {
if (await router(uri) === 0)
history.pushState(null, null, uri);
};
const router = async (uri) => {
const routes = [
{ path: "/", view: Dashboard },
{ path: "/profiles", view: ProfilesView},
{ path: "/profiles/:id", view: ProfilePageView },
{ path: "/settings", view: Settings },
{ path: "/login", view: LoginView },
{ path: "/logout", view: LogoutView },
{ path: "/register", view: RegisterView },
{ path: "/chat", view: Chat },
{ path: "/home", view: HomeView },
{ path: "/me", view: MeView },
{ path: "/matchmaking", view: MatchMakingView },
];
// Test each route for potential match
const potentialMatches = routes.map(route => {
return {
route: route,
result: uri.match(pathToRegex(route.path))
};
});
let match = potentialMatches.find(potentialMatch => potentialMatch.result !== null);
if (!match) {
match = {
route: routes[0],
result: [uri]
};
}
if (lastView !== undefined)
await lastView.leavePage();
const view = new match.route.view(getParams(match));
if (view instanceof AbstractRedirectView && await view.redirect())
return 1;
lastView = view;
let content = await view.getHtml();
if (content == null)
return 1;
view.setTitle();
document.querySelector("#app").innerHTML = content
await view.postInit();
return 0;
};
window.addEventListener("popstate", function() {router(location.pathname)});
document.addEventListener("DOMContentLoaded", () => {
document.body.addEventListener("click", e => {
if (e.target.matches("[data-link]")) {
e.preventDefault();
navigateTo(e.target.href.slice(location.origin.length));
}
});
router(location.pathname);
});
export { client, navigateTo }

View File

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

View File

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

View File

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

View File

@ -0,0 +1,20 @@
export default class {
constructor(params, title) {
this.params = params;
this.title = title;
}
async postInit() {
}
async leavePage() {
}
setTitle() {
document.title = this.title;
}
async getHtml() {
return "";
}
}

View File

@ -0,0 +1,56 @@
import AbstractAuthentifiedView from "./AbstractAuthentifiedView.js";
export default class extends AbstractAuthentifiedView {
constructor(params) {
super(params, "Chat");
let url = `wss://${window.location.host}/ws/socket-server/`
this.chatSocket = new WebSocket(url)
this.chatSocket.onmessage = function(e){
let data = JSON.parse(e.data)
console.log('Data:', data)
if (data.type === 'chat') {
let messages = document.getElementById('messages')
let username = data.username === null || data.username.length <= 0 ? "NoName" : data.username;
messages.insertAdjacentHTML('beforeend', `
<div><p>${username}: ${data.message}</p></div>
`)
}
}
}
async postInit() {
let form = document.getElementById('form')
form.addEventListener('submit', (e)=> {
e.preventDefault()
let message = e.target.message.value
this.chatSocket.send(JSON.stringify({
'message':message
}))
form.reset()
})
}
async leavePage() {
this.chatSocket.close();
}
async getHtml() {
return `
<h1>Chat</h1>
<form id="form">
<input type="text" name="message" />
</form>
<div id="messages">
</div>
`;
}
}

View File

@ -0,0 +1,19 @@
import AbstractView from "./AbstractView.js";
export default class extends AbstractView {
constructor(params) {
super(params, "Dashboard");
}
async getHtml() {
return `
<h1>Welcome back, Dom</h1>
<p>
Fugiat voluptate et nisi Lorem cillum anim sit do eiusmod occaecat irure do. Reprehenderit anim fugiat sint exercitation consequat. Sit anim laborum sit amet Lorem adipisicing ullamco duis. Anim in do magna ea pariatur et.
</p>
<p>
<a href="/posts" data-link>View recent posts</a>.
</p>
`;
}
}

View File

@ -0,0 +1,17 @@
import AbstractAuthentificateView from "./AbstractAuthentifiedView.js";
export default class extends AbstractAuthentificateView {
constructor(params) {
super(params, "Home");
this.redirect_url = "/login"
}
async getHtml() {
return `
<h1>HOME</h1>
<a href="/matchmaking" class="nav__link" data-link>Play a game</a>
<a href="/me" class="nav__link" data-link>Me</a>
<a href="/logout" class="nav__link" data-link>Logout</a>
`;
}
}

View File

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

View File

@ -0,0 +1,131 @@
import { client, navigateTo } from "../index.js";
import AbstractAuthentificateView from "./AbstractAuthentifiedView.js";
export default class extends AbstractAuthentificateView
{
constructor(params)
{
super(params, "Me");
}
async postInit()
{
if (this.fill() === null)
return;
document.getElementById("save-account-button").onclick = this.acccount_save;
document.getElementById("delete-account-button").onclick = this.account_delete_accounts;
}
async fill()
{
let data = await client.account.get();
if (data === null)
{
navigateTo("/login")
return;
}
document.getElementById("username").value = data.username;
}
async delete_accounts()
{
let current_password = document.getElementById("current_password").value;
let response_data = await client.account.delete(current_password);
if (response_data === null)
{
navigateTo("/login");
return;
}
["delete", "current_password"].forEach(error_field => {
let error_display = document.getElementById(`error_${error_field}`);
if (error_display != null)
error_display.innerHTML = "";
});
if (response_data === "user deleted")
{
document.getElementById(`error_delete`).innerHTML = "OK";
navigateTo("/login")
return;
}
document.getElementById("error_current_password").innerHTML = response_data["password"]
}
async save()
{
let username = document.getElementById("username").value;
let new_password = document.getElementById("new_password").value;
let current_password = document.getElementById("current_password").value;
let data = {};
data.username = username;
if (new_password.length != 0)
data.new_password = new_password;
let response_data = await client.account.update(data, current_password);
if (response_data === null)
{
navigateTo("/login");
return;
}
else if (response_data === "data has been alterate")
{
navigateTo("/me");
return;
}
["username", "new_password", "current_password"].forEach(error_field => {
let error_display = document.getElementById(`error_${error_field}`);
if (error_display != null)
error_display.innerHTML = "";
});
Object.keys(response_data).forEach(error_field => {
let error_display = document.getElementById(`error_${error_field}`);
if (error_display != null)
error_display.innerHTML = response_data[error_field];
});
let avatar = document.getElementById("avatar");
if (avatar.files[0] !== undefined)
{
let form_data = new FormData();
form_data.append("file", avatar.files[0]);
await client.me.change_avatar(form_data)
}
}
async getHtml()
{
return `
<link rel="stylesheet" href="static/css/me.css">
<h1>ME</h1>
<div class="account">
<h3>Account</h3>
<input type="text" placeholder="username" id="username">
<span id="error_username"></span>
<input type=password placeholder="new password" id="new_password">
<span id="error_new_password"></span>
<input type=password placeholder="current password" id="current_password">
<span id="error_current_password"></span>
<input type="button" value="Save Credentials" id="save-account-button">
<span id="error_save"></span>
<input type="button" value="Delete Account" id="delete-account-button">
<span id="error_delete"></span>
</div>
<div class="profile">
<h3>Profile</h3>
<input type="file" id="avatar" accept="image/png, image/jpeg">
<input type="button" value="Save profile" id="save-profile-button">
<span id="error_save"></span>
</div>
<a href="/logout" class="nav__link" data-link>Logout</a>
`;
}
}

View File

@ -0,0 +1,14 @@
import AbstractView from "./AbstractView.js";
export default class extends AbstractView {
constructor(params) {
super(params, "Settings");
}
async getHtml() {
return `
<h1>Settings</h1>
<p>Manage your privacy and configuration.</p>
`;
}
}

View File

@ -0,0 +1,55 @@
import { client, navigateTo } from "../../index.js";
import AbstractNonAuthentifiedView from "../AbstractNonAuthentified.js";
async function login()
{
let username = document.getElementById("username").value;
let password = document.getElementById("password").value;
let response_data = await client.login(username, password);
if (response_data == null)
{
navigateTo("/home");
return;
}
["username", "user", "password"].forEach(error_field => {
let error_display = document.getElementById(`error_${error_field}`);
if (error_display != null)
error_display.innerHTML = "";
});
Object.keys(response_data).forEach(error_field => {
let error_display = document.getElementById(`error_${error_field}`);
if (error_display != null)
error_display.innerHTML = response_data[error_field];
});
}
export default class extends AbstractNonAuthentifiedView {
constructor(params) {
super(params, "Login", "/home");
}
async postInit()
{
document.getElementById("button").onclick = login;
}
async getHtml() {
return `
<div class=form>
<label>Login</label>
<link rel="stylesheet" href="static/css/accounts/login.css">
<input type="text" id="username" placeholder="username">
<span id="error_username"></span>
<input type="password" id="password" placeholder="password">
<span id="error_password"></span>
<input type="button" value="login" id="button">
<span id="error_user"></span>
<a href="/register" class="nav__link" data-link>Register</a>
</div>
`;
}
}

View File

@ -0,0 +1,11 @@
import { client, navigateTo } from "../../index.js";
import AbstractAuthentifiedView from "../AbstractAuthentifiedView.js";
export default class extends AbstractAuthentifiedView
{
constructor(params) {
super(params, "Logout");
client.logout();
navigateTo("/login")
}
}

View File

@ -0,0 +1,55 @@
import { client, navigateTo } from "../../index.js";
import AbstractNonAuthentifiedView from "../AbstractNonAuthentified.js";
async function register()
{
let username = document.getElementById("username").value;
let password = document.getElementById("password").value;
let response_data = await client.account.create(username, password);
if (response_data == null)
{
navigateTo("/home");
return;
}
["username", "user", "password"].forEach(error_field => {
let error_display = document.getElementById(`error_${error_field}`);
if (error_display != null)
error_display.innerHTML = "";
});
Object.keys(response_data).forEach(error_field => {
let error_display = document.getElementById(`error_${error_field}`);
if (error_display != null)
error_display.innerHTML = response_data[error_field];
});
}
export default class extends AbstractNonAuthentifiedView {
constructor(params) {
super(params, "Register", "/home");
}
async postInit()
{
document.getElementById("button").onclick = register;
}
async getHtml() {
return `
<div class=form>
<label>Register</label>
<link rel="stylesheet" href="static/css/accounts/register.css">
<input type="text" id="username" placeholder="username">
<span id="error_username"></span>
<input type="password" id="password" placeholder="password">
<span id="error_password"></span>
<input type="button" value="register" id="button">
<span id="error_user"></span>
<a href="/login" class="nav__link" data-link>Login</a>
</div>
`;
}
}

View File

@ -0,0 +1,29 @@
import AbstractView from "../AbstractView.js";
import { client } from "../../index.js"
export default class extends AbstractView {
constructor(params) {
super(params, "Profile ");
this.user_id = params.id;
}
async postInit()
{
let profile = await client.profiles.getProfile(this.user_id);
let username_element = document.getElementById("username");
username_element.href = `/profiles/${this.user_id}`;
username_element.appendChild(document.createTextNode(profile.username));
let avatar_element = document.getElementById("avatar");
avatar_element.src = profile.avatar_url;
}
async getHtml() {
return `
<link rel="stylesheet" href="/static/css/profiles/profile.css">
<img id="avatar"/>
<a id="username"></a>
`;
}
}

View File

@ -0,0 +1,40 @@
import AbstractView from "../AbstractView.js";
import { client } from "../../index.js"
export default class extends AbstractView {
constructor(params) {
super(params, "Profiles");
}
async postInit()
{
let profiles = await client.profiles.all()
let profile_list_element = document.getElementById("profile-list")
profiles.forEach((profile) => {
let profile_element = document.createElement("div");
profile_element.className = "item";
let avatar = document.createElement("img");
avatar.src = profile.avatar_url;
let username = document.createElement("a");
username.href = `/profiles/${profile.user_id}`;
username.appendChild(document.createTextNode(profile.username));
profile_element.appendChild(avatar);
profile_element.appendChild(username);
profile_list_element.appendChild(profile_element)
});
}
async getHtml() {
return `
<link rel="stylesheet" href="/static/css/profiles/profiles.css">
<div id="profile-list">
</div>
`;
}
}

View File

@ -0,0 +1,21 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Single Page App</title>
<link rel="stylesheet" href="{% static 'css/index.css' %}">
</head>
<body>
<nav class="nav">
<a href="/" class="nav__link" data-link>Dashboard</a>
<a href="/profiles" class="nav__link" data-link>Profiles</a>
<a href="/login" class="nav__link" data-link>Login</a>
<a href="/register" class="nav__link" data-link>Register</a>
<a href="/chat" class="nav__link" data-link>Chat</a>
</nav>
<div id="app"></div>
<script type="module" src="{% static 'js/index.js' %}"></script>
</body>
</html>

3
frontend/tests.py Normal file
View File

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

7
frontend/urls.py Normal file
View File

@ -0,0 +1,7 @@
from django.urls import path, re_path
from .views import index_view
urlpatterns = [
re_path(r'^', index_view ,name="index"),
]

7
frontend/views.py Normal file
View File

@ -0,0 +1,7 @@
from django.shortcuts import render
# Create your views here.
def index_view(req):
return render(req, 'index.html');

3
games/admin.py Normal file
View File

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

6
games/apps.py Normal file
View File

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

14
games/models.py Normal file
View File

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

3
games/tests.py Normal file
View File

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

3
games/views.py Normal file
View File

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

3
matchmaking/admin.py Normal file
View File

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

6
matchmaking/apps.py Normal file
View File

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

48
matchmaking/consumers.py Normal file
View File

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

3
matchmaking/models.py Normal file
View File

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

6
matchmaking/routing.py Normal file
View File

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

3
matchmaking/tests.py Normal file
View File

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

3
matchmaking/views.py Normal file
View File

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

Some files were not shown because too many files have changed in this diff Show More