58 Commits

Author SHA1 Message Date
63e1520e6a add: ball respawn timeout 2023-12-12 18:25:16 +01:00
754e5867f2 welp I guess we're done 2023-12-12 15:58:50 +01:00
ca6dba2763 add: ball angle calculations 2023-12-12 12:10:48 +01:00
aa35514c44 do u guys wanna see my balls ? 2023-12-10 11:05:00 +01:00
12056554fc game: moving paddle :) 2023-12-05 12:18:34 +01:00
c2317d5404 la putain de sa mere 2023-12-04 16:51:24 +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
93 changed files with 1596 additions and 476 deletions

View File

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

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
python manage.py migrate profiles
```
- 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 .login import *
from .change_password import *
from .logout import *
from .edit 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")
]

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

@ -0,0 +1,19 @@
from rest_framework.views import APIView
from rest_framework import permissions, status
from rest_framework.response import Response
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()
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})
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")

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

@ -0,0 +1,16 @@
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):
return Response(str(request.user.is_authenticated), 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('user connected', 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,16 @@
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
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:
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())
]

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,59 @@
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 (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.'}))
{
console.log("error, client is not logged");
return null;
}
return response_data;
}
async update(data, password)
{
data.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.'}))
{
console.log("error, client is not logged");
return null;
}
return response_data;
}
}
export { Account }

View File

@ -0,0 +1,102 @@
import { Account } from "./account.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._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 login(username, password)
{
let response = await this._post("/api/accounts/login", {username: username, password: password})
let data = await response.json();
if (data == "user connected")
{
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();
return data === "True";
}
}
export {Client}

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

@ -0,0 +1,102 @@
import LoginView from "./views/accounts/LoginView.js";
import Dashboard from "./views/Dashboard.js";
import Posts from "./views/Posts.js";
import PostView from "./views/PostView.js";
import Settings from "./views/Settings.js";
import 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 GameView from "./views/Game.js"
import { Client } from "./api/client.js";
import AbstractRedirectView from "./views/AbstractRedirectView.js";
import MeView from "./views/MeView.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: "/posts", view: Posts },
{ path: "/posts/:id", view: PostView },
{ 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: "/game", view: GameView },
];
// 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", router);
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,250 @@
import AbstractView from './AbstractView.js'
export default class extends AbstractView {
constructor(params) {
super(params, 'Game');
this.game = null;
}
async getHtml() {
return `
<h1>Game</h1>
<button id='startGameButton'>Start Game</button>
<button id='stopGameButton'>Stop Game</button>
`;
}
async postInit() {
document.getElementById('startGameButton').onclick = this.startGame.bind(this);
document.getElementById('stopGameButton').onclick = this.stopGame.bind(this);
}
startGame() {
if (this.game == null) {
document.getElementById('startGameButton').innerHTML = 'Reset Game';
this.game = new Game;
}
else {
document.getElementById('app').removeChild(this.game.canvas);
this.game.cleanup();
this.game = new Game;
}
}
stopGame() {
if (!this.game)
return;
document.getElementById('app').removeChild(this.game.canvas);
document.getElementById('app').removeChild(this.game.scoresDisplay);
this.game.cleanup();
this.game = null;
document.getElementById('startGameButton').innerHTML = 'Start Game';
}
}
class Game {
constructor() {
//Global variables
this.def = {
CANVASHEIGHT: 270,
CANVASWIDTH: 480,
PADDLEHEIGHT: 70,
PADDLEWIDTH: 10,
PADDLEMARGIN: 5,
PADDLESPEED: 3,
BALLRADIUS: 5,
BALLSPEED: 2,
BALLSPEEDINCR: 0.15,
MAXBOUNCEANGLE: 10 * (Math.PI / 12),
MAXSCORE: 6
};
this.canvas = document.createElement('canvas');
this.canvas.id = 'gameCanvas';
this.canvas.width = this.def.CANVASWIDTH;
this.canvas.height = this.def.CANVASHEIGHT;
this.canvas.style.border = '1px solid #d3d3d3';
this.canvas.style.backgroundColor = '#f1f1f1';
this.context = this.canvas.getContext('2d');
document.getElementById('app').appendChild(this.canvas);
this.scoresDisplay = document.createElement('p');
this.scoresDisplay.innerHTML = 'Scores: 0 - 0';
document.getElementById('app').appendChild(this.scoresDisplay);
this.players = [
{
paddle: new Paddle(this.context,
this.def.PADDLEMARGIN,
this.def),
score: 0
},
{
paddle: new Paddle(this.context,
this.def.CANVASWIDTH - this.def.PADDLEMARGIN - this.def.PADDLEWIDTH,
this.def),
score: 0
}
];
this.ballStartSide = 0;
this.ballRespawned = false;
this.ball = new Ball(this.context, this.def, this.ballStartSide);
this.interval = setInterval(this.updateGame.bind(this), 10);
this.keys = [];
this.keyUpHandler = this.keyUpHandler.bind(this);
this.keyDownHandler = this.keyDownHandler.bind(this);
document.addEventListener('keydown', this.keyDownHandler);
document.addEventListener('keyup', this.keyUpHandler);
}
cleanup() {
clearInterval(this.interval);
document.removeEventListener('keydown', this.keyDownHandler);
document.removeEventListener('keyup', this.keyUpHandler);
this.canvas.style.display = 'none';
}
clear() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
updateGame() {
//Paddle movement
if (this.keys.includes('s') &&
this.players[0].paddle.y + this.def.PADDLEHEIGHT < this.def.CANVASHEIGHT - this.def.PADDLEMARGIN)
this.players[0].paddle.y += this.def.PADDLESPEED;
if (this.keys.includes('w') &&
this.players[0].paddle.y > 0 + this.def.PADDLEMARGIN)
this.players[0].paddle.y -= this.def.PADDLESPEED;
if (this.keys.includes('ArrowDown') &&
this.players[1].paddle.y + this.def.PADDLEHEIGHT < this.def.CANVASHEIGHT - this.def.PADDLEMARGIN)
this.players[1].paddle.y += this.def.PADDLESPEED;
if (this.keys.includes('ArrowUp') &&
this.players[1].paddle.y > 0 + this.def.PADDLEMARGIN)
this.players[1].paddle.y -= this.def.PADDLESPEED;
//GOOAAAAL
if (this.ball.x <= 0)
this.updateScore(this.players[0].score, ++this.players[1].score);
else if (this.ball.x >= this.def.CANVASWIDTH)
this.updateScore(++this.players[0].score, this.players[1].score);
//Ball collisions
if (this.detectCollision(this.players[0].paddle, this.ball))
this.calculateBallVelocity(this.players[0].paddle.getCenter().y, this.ball);
else if (this.detectCollision(this.players[1].paddle, this.ball))
this.calculateBallVelocity(this.players[1].paddle.getCenter().y, this.ball, -1);
if (this.ball.y - this.ball.radius <= 0)
this.ball.vy *= -1;
else if (this.ball.y + this.ball.radius >= this.canvas.height)
this.ball.vy *= -1;
if (!this.ballRespawned) {
this.ball.x += this.ball.vx;
this.ball.y += this.ball.vy;
}
this.clear();
this.players[0].paddle.update();
this.players[1].paddle.update();
this.ball.update();
}
updateScore(p1Score, p2Score) {
if (p1Score > this.def.MAXSCORE) {
this.scoresDisplay.innerHTML = 'Player 1 wins!! GGS';
this.cleanup();
}
else if (p2Score > this.def.MAXSCORE) {
this.scoresDisplay.innerHTML = 'Player 2 wins!! GGS';
this.cleanup();
} else {
this.scoresDisplay.innerHTML = `Scores: ${p1Score} - ${p2Score}`;
this.ballStartSide = 1 - this.ballStartSide;
this.ball = new Ball(this.context, this.def, this.ballStartSide);
this.ballRespawned = true;
new Promise(r => setTimeout(r, 300))
.then(_ => this.ballRespawned = false);
}
}
detectCollision(paddle, ball) {
let paddleCenter = paddle.getCenter();
let dx = Math.abs(ball.x - paddleCenter.x);
let dy = Math.abs(ball.y - paddleCenter.y);
if (dx <= ball.radius + paddle.width / 2 &&
dy <= ball.radius + paddle.height / 2)
return true;
return false;
}
calculateBallVelocity(paddleCenterY, ball, side = 1) {
let relativeIntersectY = paddleCenterY - ball.y;
let normRelIntersectY = relativeIntersectY / this.def.PADDLEHEIGHT / 2;
let bounceAngle = normRelIntersectY * this.def.MAXBOUNCEANGLE;
ball.speed += this.def.BALLSPEEDINCR;
ball.vx = ball.speed * side * Math.cos(bounceAngle);
ball.vy = ball.speed * -Math.sin(bounceAngle);
}
keyUpHandler(ev) {
const idx = this.keys.indexOf(ev.key);
if (idx != -1)
this.keys.splice(idx, 1);
}
keyDownHandler(ev) {
if (!this.keys.includes(ev.key))
this.keys.push(ev.key);
}
}
class Paddle {
constructor(context, paddleSide, def) {
this.width = def.PADDLEWIDTH;
this.height = def.PADDLEHEIGHT;
this.x = paddleSide;
this.y = def.CANVASHEIGHT / 2 - this.height / 2;
this.ctx = context;
this.update();
}
update() {
this.ctx.fillStyle = 'black';
this.ctx.fillRect(this.x, this.y, this.width, this.height);
}
getCenter() {
return {
x: this.x + this.width / 2,
y: this.y + this.height / 2
};
}
}
class Ball {
constructor(context, def, startSide) {
this.radius = def.BALLRADIUS;
this.speed = def.BALLSPEED;
this.x = def.CANVASWIDTH / 2;
this.y = def.CANVASHEIGHT / 2;
this.vy = 0;
if (startSide === 0)
this.vx = -this.speed;
else
this.vx = this.speed;
this.ctx = context;
this.update();
}
update() {
this.ctx.fillStyle = 'black';
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
this.ctx.fill();
}
}

View File

@ -0,0 +1,16 @@
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="/me" class="nav__link" data-link>Me</a>
<a href="/logout" class="nav__link" data-link>Logout</a>
`;
}
}

View File

@ -0,0 +1,103 @@
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-button").onclick = this.save;
document.getElementById("delete-button").onclick = this.delete_accounts;
}
async fill()
{
let data = await client.account.get();
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(super.redirect_url);
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];
});
}
async getHtml()
{
return `
<h1>ME</h1>
<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" id="save-button">
<span id="error_save"></span>
<input type="button" value="Delete" id="delete-button">
<span id="error_delete"></span>
<a href="/logout" class="nav__link" data-link>Logout</a>
`;
}
}

View File

@ -0,0 +1,15 @@
import AbstractView from "./AbstractView.js";
export default class extends AbstractView {
constructor(params) {
super(params, "Viewing Post");
this.postId = params.id;
}
async getHtml() {
return `
<h1>Post</h1>
<p>You are viewing post #${this.postId}.</p>
`;
}
}

View File

@ -0,0 +1,14 @@
import AbstractView from "./AbstractView.js";
export default class extends AbstractView {
constructor(params) {
super(params, "Posts");
}
async getHtml() {
return `
<h1>Posts</h1>
<p>You are viewing the posts!</p>
`;
}
}

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,56 @@
import { client, navigateTo } from "../../index.js";
import AbstractAuthentifiedView 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 AbstractAuthentifiedView {
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,22 @@
{% 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="/posts" class="nav__link" data-link>Posts</a>
<a href="/settings" class="nav__link" data-link>Settings</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');

6
profiles/admin.py Normal file
View File

@ -0,0 +1,6 @@
from django.contrib import admin
from .models import ProfileModel
# Register your models here.
admin.site.register(ProfileModel)

16
profiles/models.py Normal file
View File

@ -0,0 +1,16 @@
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
# Create your models here.
class ProfileModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=40)
@receiver(post_save, sender=User)
def on_user_created(sender, instance, created, **kwargs):
if created:
profile: ProfileModel = ProfileModel.objects.create(pk = instance.pk, user = instance)
profile.save()

1
profiles/status_code.py Normal file
View File

@ -0,0 +1 @@
PROFILE_NOT_FOUND = "Profile Not Found"

18
profiles/tests.py Normal file
View File

@ -0,0 +1,18 @@
from django.test import TestCase
from django.http import HttpResponse, HttpRequest
from django.contrib.auth.models import User
# Create your tests here.
class ProfileTest(TestCase):
def setUp(self):
self.user: User = User.objects.create(username='bozo', password='password')
self.user.save()
self.expected_response = {"name": "bozo",
"title": ""}
self.url = "/api/profiles/"
def test_profile_create_on_user_created(self):
response: HttpResponse = self.client.get(self.url + str(self.user.pk))
response_dict: dict = eval(response.content)
self.assertDictEqual(self.expected_response, response_dict)

7
profiles/urls.py Normal file
View File

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

19
profiles/views.py Normal file
View File

@ -0,0 +1,19 @@
from django.http import HttpRequest
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from .models import ProfileModel
# Create your views here.
class ProfileView(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request: HttpRequest, pk: int):
profile: ProfileModel = ProfileModel.objects.get(pk=pk)
if (profile is None):
return Response(status=status.HTTP_404_NOT_FOUND)
return Response(status=status.HTTP_200_OK, data={'name': profile.user.username,
'title': profile.title})

10
requirements.txt Normal file
View File

@ -0,0 +1,10 @@
asgiref==3.7.2
Django==4.2.6
django-cors-headers==4.3.0
django-restapi==0.1.3
djangorestframework==3.14.0
install==1.3.5
pytz==2023.3.post1
sqlparse==0.4.4
channels==4.0.0
daphne

View File

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

View File

@ -25,17 +25,28 @@ SECRET_KEY = 'django-insecure-18!@88-wm-!skec9^n-85n(f$my^#mh3!#@f=_e@=*arh_yyjj
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
CSRF_TRUSTED_ORIGINS = ['https://code.chauvet.pro', 'https://django.chauvet.pro']
ALLOWED_HOSTS = ["*"]
CORS_ORIGIN_ALLOW_ALL = False
CSRF_TRUSTED_ORIGINS = ["https://django.chauvet.pro"]
CORS_ORIGIN_WHITELIST = (
'http://localhost:8000',
)
# Application definition
INSTALLED_APPS = [
'channels',
'daphne',
'accounts.apps.AccountsConfig',
'profiles.apps.ProfilesConfig',
'frontend.apps.FrontendConfig',
'corsheaders',
'rest_framework',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@ -44,7 +55,17 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
]
ASGI_APPLICATION = 'trancendence.asgi.application'
CHANNEL_LAYERS = {
'default' :{
'BACKEND':'channels.layers.InMemoryChannelLayer'
}
}
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',

View File

@ -19,6 +19,7 @@ from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/profiles/', include('profiles.urls')),
path('api/accounts/', include('accounts.urls')),
path('api/profiles/', include('accounts.urls')),
path('', include('frontend.urls')),
]