3 Commits

Author SHA1 Message Date
208dd206ce merge with server 2023-12-11 10:57:51 +01:00
cee188145d no idea 2023-12-11 10:53:34 +01:00
af9595c447 Don't merge, it's prototypal 2023-11-30 16:36:21 +01:00
34 changed files with 76 additions and 239 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@
db.sqlite3 db.sqlite3
**/migrations/** **/migrations/**
/profiles/static/avatars/* /profiles/static/avatars/*
!/profiles/static/avatars/default !/profiles/static/avatars/default.env

View File

@ -22,8 +22,8 @@ pip install -r requirements.txt
``` ```
- Setup database - Setup database
``` ```
python manage.py runserver makemigrations profiles games python manage.py runserver makemigrations profiles
python manage.py migrate profiles games python manage.py migrate profiles
``` ```
- Start the developpement server - Start the developpement server
``` ```

View File

@ -1,7 +1,6 @@
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework import permissions, status from rest_framework import permissions, status
from rest_framework.response import Response from rest_framework.response import Response
from django.contrib.auth import logout
from django.http import HttpRequest from django.http import HttpRequest
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
@ -17,5 +16,4 @@ class DeleteView(APIView):
if (request.user.check_password(password) == False): if (request.user.check_password(password) == False):
return Response({"password": ["Password wrong."]}) return Response({"password": ["Password wrong."]})
request.user.delete() request.user.delete()
logout(request)
return Response("user deleted", status=status.HTTP_200_OK) return Response("user deleted", status=status.HTTP_200_OK)

View File

@ -1,19 +0,0 @@
#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

@ -1,5 +1,4 @@
import { Account } from "./account.js"; import { Account } from "./account.js";
import { MatchMaking } from "./matchmaking.js";
import { Profile } from "./profile.js"; import { Profile } from "./profile.js";
import { Profiles } from "./profiles.js"; import { Profiles } from "./profiles.js";
@ -20,7 +19,6 @@ class Client
this._url = url; this._url = url;
this.account = new Account(this); this.account = new Account(this);
this.profiles = new Profiles(this); this.profiles = new Profiles(this);
this.matchmaking = new MatchMaking(this);
this._logged = undefined; this._logged = undefined;
} }

View File

@ -1,42 +0,0 @@
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

@ -8,19 +8,19 @@ class Profile
this.user_id = user_id this.user_id = user_id
} }
async init(user_id) async init(id)
{ {
let response = await this.client._get(`/api/profiles/${user_id}`); let response = await this.client._get(`/api/profiles/${id}`);
let response_data = await response.json(); let response_data = await response.json();
this.user_id = user_id; this.id = id;
this.username = response_data.username; this.username = response_data.username;
this.avatar_url = response_data.avatar_url; this.avatar_url = response_data.avatar_url;
} }
async change_avatar(form_data) async change_avatar(form_data)
{ {
let response = await this.client._patch_file(`/api/profiles/${this.user_id}`, form_data); let response = await this.client._patch_file(`/api/profiles/${this.id}`, form_data);
let response_data = await response.json() let response_data = await response.json()
return response_data; return response_data;

View File

@ -1,6 +1,7 @@
import LoginView from "./views/accounts/LoginView.js"; import LoginView from "./views/accounts/LoginView.js";
import Dashboard from "./views/Dashboard.js"; import Dashboard from "./views/Dashboard.js";
import Settings from "./views/Settings.js"; import Settings from "./views/Settings.js";
import Search from "./views/Search.js";
import Chat from "./views/Chat.js"; import Chat from "./views/Chat.js";
import HomeView from "./views/HomeView.js"; import HomeView from "./views/HomeView.js";
import RegisterView from "./views/accounts/RegisterView.js"; import RegisterView from "./views/accounts/RegisterView.js";
@ -11,7 +12,6 @@ import AbstractRedirectView from "./views/AbstractRedirectView.js";
import MeView from "./views/MeView.js"; import MeView from "./views/MeView.js";
import ProfilePageView from "./views/profiles/ProfilePageView.js"; import ProfilePageView from "./views/profiles/ProfilePageView.js";
import ProfilesView from "./views/profiles/ProfilesView.js"; import ProfilesView from "./views/profiles/ProfilesView.js";
import MatchMakingView from "./views/MatchMakingView.js";
let client = new Client(location.protocol + "//" + location.host) let client = new Client(location.protocol + "//" + location.host)
@ -33,7 +33,7 @@ const navigateTo = async (uri) => {
history.pushState(null, null, uri); history.pushState(null, null, uri);
}; };
const router = async (uri) => { const router = async (uri = "") => {
const routes = [ const routes = [
{ path: "/", view: Dashboard }, { path: "/", view: Dashboard },
{ path: "/profiles", view: ProfilesView}, { path: "/profiles", view: ProfilesView},
@ -42,10 +42,10 @@ const router = async (uri) => {
{ path: "/login", view: LoginView }, { path: "/login", view: LoginView },
{ path: "/logout", view: LogoutView }, { path: "/logout", view: LogoutView },
{ path: "/register", view: RegisterView }, { path: "/register", view: RegisterView },
{ path: "/search", view: Search },
{ path: "/chat", view: Chat }, { path: "/chat", view: Chat },
{ path: "/home", view: HomeView }, { path: "/home", view: HomeView },
{ path: "/me", view: MeView }, { path: "/me", view: MeView },
{ path: "/matchmaking", view: MatchMakingView },
]; ];
// Test each route for potential match // Test each route for potential match
@ -86,7 +86,7 @@ const router = async (uri) => {
return 0; return 0;
}; };
window.addEventListener("popstate", function() {router(location.pathname)}); window.addEventListener("popstate", router);
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
document.body.addEventListener("click", e => { document.body.addEventListener("click", e => {

View File

@ -4,7 +4,7 @@ export default class extends AbstractAuthentifiedView {
constructor(params) { constructor(params) {
super(params, "Chat"); super(params, "Chat");
let url = `wss://${window.location.host}/ws/socket-server/` let url = `ws://${window.location.host}/ws/socket-server/`
this.chatSocket = new WebSocket(url) this.chatSocket = new WebSocket(url)
this.chatSocket.onmessage = function(e){ this.chatSocket.onmessage = function(e){
@ -44,7 +44,7 @@ export default class extends AbstractAuthentifiedView {
<h1>Chat</h1> <h1>Chat</h1>
<form id="form"> <form id="form">
<input type="text" name="message" /> <input type="text" name="message" placeholder="message"/>
</form> </form>
<div id="messages"> <div id="messages">

View File

@ -9,7 +9,6 @@ export default class extends AbstractAuthentificateView {
async getHtml() { async getHtml() {
return ` return `
<h1>HOME</h1> <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="/me" class="nav__link" data-link>Me</a>
<a href="/logout" class="nav__link" data-link>Logout</a> <a href="/logout" class="nav__link" data-link>Logout</a>
`; `;

View File

@ -1,30 +0,0 @@
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

@ -12,8 +12,8 @@ export default class extends AbstractAuthentificateView
{ {
if (this.fill() === null) if (this.fill() === null)
return; return;
document.getElementById("save-account-button").onclick = this.acccount_save; document.getElementById("save-button").onclick = this.save;
document.getElementById("delete-account-button").onclick = this.account_delete_accounts; document.getElementById("delete-button").onclick = this.delete_accounts;
} }
async fill() async fill()
@ -104,27 +104,22 @@ export default class extends AbstractAuthentificateView
async getHtml() async getHtml()
{ {
return ` return `
<link rel="stylesheet" href="static/css/me.css">
<h1>ME</h1> <h1>ME</h1>
<div class="account"> <div class="accounts">
<h3>Account</h3>
<input type="text" placeholder="username" id="username"> <input type="text" placeholder="username" id="username">
<span id="error_username"></span> <span id="error_username"></span>
<input type=password placeholder="new password" id="new_password"> <input type=password placeholder="new password" id="new_password">
<span id="error_new_password"></span> <span id="error_new_password"></span>
<input type=password placeholder="current password" id="current_password"> <input type=password placeholder="current password" id="current_password">
<span id="error_current_password"></span> <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>
<div class="profile"> <div class="profile">
<h3>Profile</h3> <input type="file" placeholder="username" id="avatar" accept="image/png, image/jpeg">
<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> </div>
<input type="button" value="Save" id="save-button">
<span id="error_save"></span>
<input type="button" value="Delete" id="delete-button">
<span id="error_delete"></span>
<a href="/logout" class="nav__link" data-link>Logout</a> <a href="/logout" class="nav__link" data-link>Logout</a>
`; `;
} }

View File

@ -0,0 +1,38 @@
import AbstractAuthentifiedView from "./AbstractAuthentifiedView.js";
export default class extends AbstractAuthentifiedView {
constructor(params) {
super(params, "Search");
}
async postInit() {
let users = ["cramptéMan", "cacaMan", "chatteWomen"]
let list_users = document.getElementById('list_users');
for (const user of users) {
var new_user = document.createElement("li");
new_user.appendChild(document.createTextNode(user));
list_users.appendChild(new_user);
}
console.log(list_users);
}
async leavePage() {
}
async getHtml() {
return `
<h1>Search</h1>
<form id="form">
<input type="text" name="message" placeholder="user name to crampte"/>
</form>
<div id="users">
<ul id="list_users">
</ul>
</div>
`;
}
}

View File

@ -13,7 +13,7 @@
<a href="/profiles" class="nav__link" data-link>Profiles</a> <a href="/profiles" class="nav__link" data-link>Profiles</a>
<a href="/login" class="nav__link" data-link>Login</a> <a href="/login" class="nav__link" data-link>Login</a>
<a href="/register" class="nav__link" data-link>Register</a> <a href="/register" class="nav__link" data-link>Register</a>
<a href="/chat" class="nav__link" data-link>Chat</a> <a href="/search" class="nav__link" data-link>Search</a>
</nav> </nav>
<div id="app"></div> <div id="app"></div>
<script type="module" src="{% static 'js/index.js' %}"></script> <script type="module" src="{% static 'js/index.js' %}"></script>

View File

View File

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

View File

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

View File

@ -1,14 +0,0 @@
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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "ft_transcendence",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@ -11,7 +11,7 @@ def upload_to(instance, filename: str):
# Create your models here. # Create your models here.
class ProfileModel(models.Model): class ProfileModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
avatar_url = models.ImageField(upload_to=upload_to, default="./profiles/static/avatars/default.avif") #blank=True, null=True) avatar_url = models.ImageField(upload_to=upload_to, default="../static/avatars/default.avif") #blank=True, null=True)
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def on_user_created(sender, instance, created, **kwargs): def on_user_created(sender, instance, created, **kwargs):

View File

@ -11,19 +11,19 @@ from .serializers import ProfileSerializer
from .models import ProfileModel from .models import ProfileModel
class ProfileViewSet(viewsets.ModelViewSet): class ProfileViewSet(viewsets.ModelViewSet):
queryset = ProfileModel.objects.all queryset = ProfileModel.objects.all()
serializer_class = ProfileSerializer serializer_class = ProfileSerializer
parser_classes = (MultiPartParser, FormParser) parser_classes = (MultiPartParser, FormParser)
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def retrieve(self, request: HttpRequest, pk=None): def retrieve(self, request: HttpRequest, pk=None):
instance = ProfileModel.objects.get(pk=pk) instance = self.get_object()
instance.avatar_url.name = instance.avatar_url.name[instance.avatar_url.name.find("static") - 1:] instance.avatar_url.name = instance.avatar_url.name[instance.avatar_url.name.find("static") - 1:]
return Response(self.serializer_class(instance).data, return Response(self.serializer_class(instance).data,
status=status.HTTP_200_OK) status=status.HTTP_200_OK)
def list(self, request: HttpRequest): def list(self, request: HttpRequest):
serializer = ProfileSerializer(self.queryset(), many=True) serializer = ProfileSerializer(self.queryset, many=True)
for profile in serializer.data: for profile in serializer.data:
profile["avatar_url"] = profile["avatar_url"][profile["avatar_url"].find("static") - 1:] profile["avatar_url"] = profile["avatar_url"][profile["avatar_url"].find("static") - 1:]
return Response(serializer.data) return Response(serializer.data)
@ -38,7 +38,7 @@ class ProfileViewSet(viewsets.ModelViewSet):
profile: ProfileModel = ProfileModel.objects.get(pk=self.request.user.pk) profile: ProfileModel = ProfileModel.objects.get(pk=self.request.user.pk)
avatar = self.request.data.get("file", None) avatar = self.request.data.get("file", None)
if (avatar is not None): if (avatar is not None):
if (profile.avatar_url.name != "./profiles/static/avatars/default.avif"): if (profile.avatar_url.name != "default.avif"):
profile.avatar_url.storage.delete(profile.avatar_url.name) profile.avatar_url.storage.delete(profile.avatar_url.name)
profile.avatar_url = avatar profile.avatar_url = avatar
profile.save() profile.save()

View File

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

View File

@ -43,8 +43,6 @@ INSTALLED_APPS = [
'channels', 'channels',
'daphne', 'daphne',
'matchmaking.apps.MatchmakingConfig',
'games.apps.GamesConfig',
'accounts.apps.AccountsConfig', 'accounts.apps.AccountsConfig',
'profiles.apps.ProfilesConfig', 'profiles.apps.ProfilesConfig',
'frontend.apps.FrontendConfig', 'frontend.apps.FrontendConfig',