Compare commits
76 Commits
a6a86a67a5
...
feat/match
Author | SHA1 | Date | |
---|---|---|---|
ae20be25fb | |||
ad6cfdf08a | |||
08ce682980 | |||
982130a02f | |||
326724930a | |||
85787760b9 | |||
fb1b71ade6 | |||
2ccfc5464a | |||
d8a279f4d8 | |||
336257d1d0 | |||
f66b3883c1 | |||
9c1dd30db7 | |||
079be0bb46 | |||
df436e0b88 | |||
c3c83b3168 | |||
dd19e15e7d | |||
0edcd97f94 | |||
54afa8aae5 | |||
2ce1356412 | |||
d1c75f7033 | |||
3833c647aa | |||
32a8cbfda6 | |||
9b6c5547f0 | |||
910644a804 | |||
25f315c24f | |||
c2b6dbb989 | |||
086c20bddc | |||
a9cdde963d | |||
3403577c3e | |||
5d8005df44 | |||
65a027014b | |||
bf3393e9a9 | |||
9ae0dd0e28 | |||
d64e62101a | |||
84a5a592ca | |||
a6666b889f | |||
9947ea37e2 | |||
7e34f883aa | |||
07d06253ba | |||
d5e692449b | |||
6dc0293455 | |||
b12c03074a | |||
8bce7d33ca | |||
b5b54a98ba | |||
25721bdda8 | |||
27044e9bdb | |||
3f3ab52a09 | |||
ffbfe2ddd0 | |||
9c59401cf2 | |||
d490208ffa | |||
2a468bcb82 | |||
7b6a8ba57b | |||
27e75c4497 | |||
a965adfdce | |||
c16d281892 | |||
56b6f0e138 | |||
8202a8b88d | |||
6a4e161d71 | |||
b98371cf70 | |||
b95bed8894 | |||
ea42d10ddf | |||
267eeab896 | |||
4a5b44f0e2 | |||
45b782e94c | |||
36d97eb58c | |||
9aa724e19e | |||
b9728dcb06 | |||
a7d9471d59 | |||
eb8789aa1d | |||
9d8476ebb8 | |||
16c47b47cf | |||
827743bb96 | |||
a0c0d813b6 | |||
54cc1b1705 | |||
246213c5b8 | |||
e253631f09 |
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.env
|
||||
*.pyc
|
||||
db.sqlite3
|
||||
**/migrations/**
|
||||
/profiles/static/avatars/*
|
||||
!/profiles/static/avatars/default
|
31
README.md
Normal file
31
README.md
Normal 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
|
||||
```
|
12
accounts/serializers/login.py
Normal file
12
accounts/serializers/login.py
Normal 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
|
12
accounts/serializers/register.py
Normal file
12
accounts/serializers/register.py
Normal 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
|
@ -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
37
accounts/tests/delete.py
Normal 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
49
accounts/tests/edit.py
Normal 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
53
accounts/tests/login.py
Normal 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
17
accounts/tests/logout.py
Normal 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)
|
52
accounts/tests/register.py
Normal file
52
accounts/tests/register.py
Normal 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
13
accounts/urls.py
Normal 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
21
accounts/views/delete.py
Normal 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
45
accounts/views/edit.py
Normal 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
18
accounts/views/logged.py
Normal 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
23
accounts/views/login.py
Normal 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
13
accounts/views/logout.py
Normal 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)
|
18
accounts/views/register.py
Normal file
18
accounts/views/register.py
Normal 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
6
chat/apps.py
Normal 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
36
chat/consumers.py
Normal 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
6
chat/routing.py
Normal 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
4
django/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
.env
|
||||
*.pyc
|
||||
db.sqlite3
|
||||
**/migrations/*
|
@ -1,3 +0,0 @@
|
||||
asgiref==3.7.2
|
||||
Django==4.2.6
|
||||
sqlparse==0.4.4
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -1,4 +0,0 @@
|
||||
PASSWORD_MIN_SIZE = 3
|
||||
PASSWORD_MAX_SIZE = 128
|
||||
USERNAME_MIN_SIZE = 3
|
||||
USERNAME_MAX_SIZE = 40
|
@ -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"
|
@ -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>
|
@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<form method='post'>
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type='submit'>
|
||||
</form>
|
||||
</html>
|
@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<form method='post'>
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type='submit'>
|
||||
</form>
|
||||
</html>
|
@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<form method='post'>
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<input type='submit'>
|
||||
</form>
|
||||
</html>
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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"),
|
||||
]
|
@ -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)
|
@ -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)
|
||||
|
@ -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)
|
@ -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)
|
@ -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
3
frontend/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
frontend/apps.py
Normal file
6
frontend/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FrontendConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'frontend'
|
@ -1,3 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
# Create your models here.
|
12
frontend/static/css/accounts/login.css
Normal file
12
frontend/static/css/accounts/login.css
Normal 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;
|
||||
}
|
12
frontend/static/css/accounts/register.css
Normal file
12
frontend/static/css/accounts/register.css
Normal 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;
|
||||
}
|
9
frontend/static/css/index.css
Normal file
9
frontend/static/css/index.css
Normal file
@ -0,0 +1,9 @@
|
||||
body {
|
||||
margin: 10;
|
||||
font-family: 'Quicksand', sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #009579;
|
||||
}
|
19
frontend/static/css/me.css
Normal file
19
frontend/static/css/me.css
Normal 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;
|
||||
}
|
11
frontend/static/css/profiles/profile.css
Normal file
11
frontend/static/css/profiles/profile.css
Normal file
@ -0,0 +1,11 @@
|
||||
#app #avatar
|
||||
{
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#app #username
|
||||
{
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
11
frontend/static/css/profiles/profiles.css
Normal file
11
frontend/static/css/profiles/profiles.css
Normal file
@ -0,0 +1,11 @@
|
||||
#app .item img
|
||||
{
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#app .item a
|
||||
{
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
65
frontend/static/js/api/account.js
Normal file
65
frontend/static/js/api/account.js
Normal 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 }
|
127
frontend/static/js/api/client.js
Normal file
127
frontend/static/js/api/client.js
Normal 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}
|
42
frontend/static/js/api/matchmaking.js
Normal file
42
frontend/static/js/api/matchmaking.js
Normal 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}
|
35
frontend/static/js/api/profile.js
Normal file
35
frontend/static/js/api/profile.js
Normal 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}
|
30
frontend/static/js/api/profiles.js
Normal file
30
frontend/static/js/api/profiles.js
Normal 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
101
frontend/static/js/index.js
Normal 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 }
|
18
frontend/static/js/views/AbstractAuthentifiedView.js
Normal file
18
frontend/static/js/views/AbstractAuthentifiedView.js
Normal 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;
|
||||
}
|
||||
}
|
16
frontend/static/js/views/AbstractNonAuthentified.js
Normal file
16
frontend/static/js/views/AbstractNonAuthentified.js
Normal 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;
|
||||
}
|
||||
}
|
15
frontend/static/js/views/AbstractRedirectView.js
Normal file
15
frontend/static/js/views/AbstractRedirectView.js
Normal 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);
|
||||
}
|
||||
}
|
20
frontend/static/js/views/AbstractView.js
Normal file
20
frontend/static/js/views/AbstractView.js
Normal 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 "";
|
||||
}
|
||||
}
|
56
frontend/static/js/views/Chat.js
Normal file
56
frontend/static/js/views/Chat.js
Normal 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>
|
||||
|
||||
`;
|
||||
}
|
||||
}
|
19
frontend/static/js/views/Dashboard.js
Normal file
19
frontend/static/js/views/Dashboard.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
17
frontend/static/js/views/HomeView.js
Normal file
17
frontend/static/js/views/HomeView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
30
frontend/static/js/views/MatchMakingView.js
Normal file
30
frontend/static/js/views/MatchMakingView.js
Normal 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();
|
||||
}
|
||||
}
|
131
frontend/static/js/views/MeView.js
Normal file
131
frontend/static/js/views/MeView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
14
frontend/static/js/views/Settings.js
Normal file
14
frontend/static/js/views/Settings.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
55
frontend/static/js/views/accounts/LoginView.js
Normal file
55
frontend/static/js/views/accounts/LoginView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
11
frontend/static/js/views/accounts/LogoutView.js
Normal file
11
frontend/static/js/views/accounts/LogoutView.js
Normal 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")
|
||||
}
|
||||
}
|
55
frontend/static/js/views/accounts/RegisterView.js
Normal file
55
frontend/static/js/views/accounts/RegisterView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
29
frontend/static/js/views/profiles/ProfilePageView.js
Normal file
29
frontend/static/js/views/profiles/ProfilePageView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
40
frontend/static/js/views/profiles/ProfilesView.js
Normal file
40
frontend/static/js/views/profiles/ProfilesView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
21
frontend/templates/index.html
Normal file
21
frontend/templates/index.html
Normal 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
3
frontend/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
7
frontend/urls.py
Normal file
7
frontend/urls.py
Normal 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
7
frontend/views.py
Normal 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
3
games/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
games/apps.py
Normal file
6
games/apps.py
Normal 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
14
games/models.py
Normal 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
3
games/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
games/views.py
Normal file
3
games/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
3
matchmaking/admin.py
Normal file
3
matchmaking/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
matchmaking/apps.py
Normal file
6
matchmaking/apps.py
Normal 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
48
matchmaking/consumers.py
Normal 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
3
matchmaking/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
6
matchmaking/routing.py
Normal file
6
matchmaking/routing.py
Normal 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
3
matchmaking/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
matchmaking/views.py
Normal file
3
matchmaking/views.py
Normal 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
Reference in New Issue
Block a user