Compare commits

...

No commits in common. "main" and "feat/matchmaking" have entirely different histories.

93 changed files with 2016 additions and 3 deletions

6
.gitignore vendored Normal file
View File

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

View File

@ -1,4 +1,31 @@
# TRANSCENDENCE
The last project of the 42 common core
# BACKEND
# adrien est une merde
## 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
```

0
accounts/__init__.py Normal file
View File

3
accounts/admin.py Normal file
View File

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

6
accounts/apps.py Normal file
View File

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

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

@ -0,0 +1,5 @@
from .register import *
from .login 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")
]

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

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

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

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

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

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

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

@ -0,0 +1,23 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from django.http import HttpRequest
from django.contrib.auth import login
from rest_framework.authentication import SessionAuthentication
from ..serializers.login import LoginSerializer
class LoginView(APIView):
permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,)
def post(self, request: HttpRequest):
data = request.data
serializer = LoginSerializer(data=data)
serializer.is_valid(raise_exception=True)
user = serializer.get_user(data)
if user is None:
return Response({'user': ['Username or password wrong.']}, status.HTTP_200_OK)
login(request, user)
return Response({'id': user.pk}, status=status.HTTP_200_OK)

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

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

View File

@ -0,0 +1,18 @@
from rest_framework import permissions, status
from ..serializers.register import RegisterSerialiser
from rest_framework.views import APIView
from rest_framework.response import Response
from django.http import HttpRequest
from django.contrib.auth import login
class RegisterView(APIView):
permission_classes = (permissions.AllowAny,)
def post(self, request: HttpRequest):
data = request.data
serializer = RegisterSerialiser(data=data)
if serializer.is_valid(raise_exception=True):
user = serializer.create(data)
if user:
login(request, user)
return Response("user created", status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)

0
chat/__init__.py Normal file
View File

3
chat/admin.py Normal file
View File

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

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
}))

3
chat/models.py Normal file
View File

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

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())
]

3
chat/tests.py Normal file
View File

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

3
chat/views.py Normal file
View File

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

0
frontend/__init__.py Normal file
View File

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'

3
frontend/models.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

3
frontend/tests.py Normal file
View File

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

7
frontend/urls.py Normal file
View File

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

7
frontend/views.py Normal file
View File

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

0
games/__init__.py Normal file
View File

3
games/admin.py Normal file
View File

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

6
games/apps.py Normal file
View File

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

14
games/models.py Normal file
View File

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

3
games/tests.py Normal file
View File

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

3
games/views.py Normal file
View File

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

22
manage.py Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

0
matchmaking/__init__.py Normal file
View File

3
matchmaking/admin.py Normal file
View File

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

6
matchmaking/apps.py Normal file
View File

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

48
matchmaking/consumers.py Normal file
View File

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

3
matchmaking/models.py Normal file
View File

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

6
matchmaking/routing.py Normal file
View File

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

3
matchmaking/tests.py Normal file
View File

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

3
matchmaking/views.py Normal file
View File

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

0
profiles/__init__.py Normal file
View File

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)

6
profiles/apps.py Normal file
View File

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

20
profiles/models.py Normal file
View File

@ -0,0 +1,20 @@
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
from django.conf import settings
def upload_to(instance, filename: str):
return f"./profiles/static/avatars/{instance.pk}.{filename.split('.')[1]}"
# Create your models here.
class ProfileModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
avatar_url = models.ImageField(upload_to=upload_to, default="./profiles/static/avatars/default.avif") #blank=True, null=True)
@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()

11
profiles/serializers.py Normal file
View File

@ -0,0 +1,11 @@
from rest_framework import serializers
from .models import ProfileModel
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.ReadOnlyField(source='user.username')
avatar_url = serializers.ImageField(required=False)
class Meta:
model = ProfileModel
fields = ["username", "avatar_url", "user_id"]

Binary file not shown.

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)

11
profiles/urls.py Normal file
View File

@ -0,0 +1,11 @@
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from . import viewsets
urlpatterns = [
path("<int:pk>", viewsets.ProfileViewSet.as_view({'get': 'retrieve', 'patch': 'partial_update'}), name="profile_page"),
path("", viewsets.ProfileViewSet.as_view({'get': 'list'}), name="profiles_list"),
#path("me", viewsets.ProfileViewSet.as_view(), name="my_profile_page"),
] + static("/static/avatars/", document_root="./avatars")

44
profiles/viewsets.py Normal file
View File

@ -0,0 +1,44 @@
from rest_framework import permissions
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework import permissions, status
from rest_framework import viewsets
from rest_framework.response import Response
from django.http import HttpRequest
from django.db.models import QuerySet
from .serializers import ProfileSerializer
from .models import ProfileModel
class ProfileViewSet(viewsets.ModelViewSet):
queryset = ProfileModel.objects.all
serializer_class = ProfileSerializer
parser_classes = (MultiPartParser, FormParser)
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def retrieve(self, request: HttpRequest, pk=None):
instance = ProfileModel.objects.get(pk=pk)
instance.avatar_url.name = instance.avatar_url.name[instance.avatar_url.name.find("static") - 1:]
return Response(self.serializer_class(instance).data,
status=status.HTTP_200_OK)
def list(self, request: HttpRequest):
serializer = ProfileSerializer(self.queryset(), many=True)
for profile in serializer.data:
profile["avatar_url"] = profile["avatar_url"][profile["avatar_url"].find("static") - 1:]
return Response(serializer.data)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def perform_update(self, serializer):
query: QuerySet = ProfileModel.objects.filter(pk=self.request.user.pk)
if (not query.exists()):
return Response("profile not found", status=status.HTTP_400_BAD_REQUEST)
profile: ProfileModel = ProfileModel.objects.get(pk=self.request.user.pk)
avatar = self.request.data.get("file", None)
if (avatar is not None):
if (profile.avatar_url.name != "./profiles/static/avatars/default.avif"):
profile.avatar_url.storage.delete(profile.avatar_url.name)
profile.avatar_url = avatar
profile.save()

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

0
trancendence/__init__.py Normal file
View File

30
trancendence/asgi.py Normal file
View File

@ -0,0 +1,30 @@
"""
ASGI config for trancendence project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
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
import matchmaking.routing
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings')
application = ProtocolTypeRouter({
'http':get_asgi_application(),
'websocket':AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns +
matchmaking.routing.websocket_urlpatterns
)
)
})

153
trancendence/settings.py Normal file
View File

@ -0,0 +1,153 @@
"""
Django settings for trancendence project.
Generated by 'django-admin startproject' using Django 4.2.6.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
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
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',
'matchmaking.apps.MatchmakingConfig',
'games.apps.GamesConfig',
'accounts.apps.AccountsConfig',
'profiles.apps.ProfilesConfig',
'frontend.apps.FrontendConfig',
'corsheaders',
'rest_framework',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'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',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'trancendence.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'trancendence.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

25
trancendence/urls.py Normal file
View File

@ -0,0 +1,25 @@
"""
URL configuration for trancendence project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
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('', include('frontend.urls')),
]

16
trancendence/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for trancendence project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings')
application = get_wsgi_application()