2 Commits

Author SHA1 Message Date
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
55 changed files with 149 additions and 749 deletions

2
.gitignore vendored
View File

@ -2,5 +2,3 @@
*.pyc *.pyc
db.sqlite3 db.sqlite3
**/migrations/** **/migrations/**
/profiles/static/avatars/*
!/profiles/static/avatars/default

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,5 +1,4 @@
from .register import * from .register import *
from .login import * from .login import *
from .logout import *
from .edit import * from .edit import *
from .delete import * from .delete import *

View File

@ -21,17 +21,12 @@ class DeleteTest(TestCase):
def test_normal_delete(self): def test_normal_delete(self):
response: HttpResponse = self.client.delete(self.url, {"password": self.password}, content_type='application/json') response: HttpResponse = self.client.delete(self.url)
response_text: str = response.content.decode("utf-8") response_text: str = response.content.decode("utf-8")
self.assertEqual(response_text, '"user deleted"') 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): def test_no_logged(self):
self.client.logout() self.client.logout()
response: HttpResponse = self.client.delete(self.url, {"password": self.password}, content_type='application/json') response: HttpResponse = self.client.post(self.url)
errors: dict = eval(response.content) errors: dict = eval(response.content)
self.assertDictEqual(errors, {"detail":"Authentication credentials were not provided."}) self.assertDictEqual(errors, {"detail":"Authentication credentials were not provided."})

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
@ -9,13 +8,5 @@ class DeleteView(APIView):
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def delete(self, request: HttpRequest): 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() 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

@ -12,9 +12,6 @@ class EditView(APIView):
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest):
return Response({"username": request.user.username, "id": request.user.pk})
def patch(self, request: HttpRequest): def patch(self, request: HttpRequest):
data: dict = request.data data: dict = request.data

View File

@ -13,6 +13,4 @@ class LoggedView(APIView):
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
if (request.user.is_authenticated): return Response(str(request.user.is_authenticated), status=status.HTTP_200_OK)
return Response({'id': request.user.pk}, status=status.HTTP_200_OK)
return Response('false', status=status.HTTP_200_OK)

View File

@ -20,4 +20,4 @@ class LoginView(APIView):
if user is None: if user is None:
return Response({'user': ['Username or password wrong.']}, status.HTTP_200_OK) return Response({'user': ['Username or password wrong.']}, status.HTTP_200_OK)
login(request, user) login(request, user)
return Response({'id': user.pk}, status=status.HTTP_200_OK) return Response('user connected', status=status.HTTP_200_OK)

View File

@ -3,7 +3,6 @@ from ..serializers.register import RegisterSerialiser
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from django.http import HttpRequest from django.http import HttpRequest
from django.contrib.auth import login
class RegisterView(APIView): class RegisterView(APIView):
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
@ -13,6 +12,5 @@ class RegisterView(APIView):
if serializer.is_valid(raise_exception=True): if serializer.is_valid(raise_exception=True):
user = serializer.create(data) user = serializer.create(data)
if user: if user:
login(request, user)
return Response("user created", status=status.HTTP_201_CREATED) return Response("user created", status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_400_BAD_REQUEST)

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,11 +0,0 @@
#app #avatar
{
height: 100px;
width: 100px;
}
#app #username
{
height: 100px;
width: 100px;
}

View File

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

View File

@ -1,65 +0,0 @@
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,15 @@
class Accounts
{
constructor (client)
{
this.client = client;
}
async create(username, password)
{
let response = await this.client._post("/api/accounts/register", {username: username, password: password});
return response
}
}
export { Accounts }

View File

@ -1,26 +1,11 @@
import { Account } from "./account.js"; import { Accounts } from "./accounts.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 class Client
{ {
constructor(url) constructor(url)
{ {
this._url = url; this._url = url;
this.account = new Account(this); this.accounts = new Accounts(this);
this.profiles = new Profiles(this);
this.matchmaking = new MatchMaking(this);
this._logged = undefined; this._logged = undefined;
} }
@ -45,59 +30,18 @@ class Client
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-CSRFToken": getCookie("csrftoken"),
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
}); });
return response; 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) async login(username, password)
{ {
let response = await this._post("/api/accounts/login", {username: username, password: password}) let response = await this._post("/api/accounts/login", {username: username, password: password})
let data = await response.json(); let data = await response.json();
if (data.id != undefined) if (data == "user connected")
{ {
this.me = new Profile(this)
await this.me.init(data.id)
this.logged = true; this.logged = true;
return null; return null;
} }
@ -114,13 +58,7 @@ class Client
{ {
let response = await this._get("/api/accounts/logged"); let response = await this._get("/api/accounts/logged");
let data = await response.json(); let data = await response.json();
return data === "True";
if (data.id !== undefined)
{
this.me = new Profile(this)
await this.me.init(data.id)
}
return data.id !== 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

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

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

View File

@ -1,6 +1,9 @@
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 Posts from "./views/Posts.js";
import PostView from "./views/PostView.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";
@ -8,10 +11,6 @@ import LogoutView from "./views/accounts/LogoutView.js";
import { Client } from "./api/client.js"; import { Client } from "./api/client.js";
import AbstractRedirectView from "./views/AbstractRedirectView.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 client = new Client(location.protocol + "//" + location.host)
@ -33,19 +32,18 @@ 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: "/posts", view: Posts },
{ path: "/profiles/:id", view: ProfilePageView }, { path: "/posts/:id", view: PostView },
{ path: "/settings", view: Settings }, { path: "/settings", view: Settings },
{ 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: "/matchmaking", view: MatchMakingView },
]; ];
// Test each route for potential match // Test each route for potential match
@ -86,7 +84,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,8 +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="/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

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

View File

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

View File

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

@ -1,18 +1,13 @@
import { client, navigateTo } from "../../index.js"; import { client, navigateTo } from "../../index.js";
import AbstractNonAuthentifiedView from "../AbstractNonAuthentified.js"; import AbstractAuthentifiedView from "../AbstractNonAuthentified.js";
async function register() async function register()
{ {
let username = document.getElementById("username").value; let username = document.getElementById("username").value;
let password = document.getElementById("password").value; let password = document.getElementById("password").value;
let response_data = await client.account.create(username, password); let response = await client.accounts.create(username, password);
let response_data = await response.json();
if (response_data == null)
{
navigateTo("/home");
return;
}
["username", "user", "password"].forEach(error_field => { ["username", "user", "password"].forEach(error_field => {
let error_display = document.getElementById(`error_${error_field}`); let error_display = document.getElementById(`error_${error_field}`);
@ -27,7 +22,7 @@ async function register()
}); });
} }
export default class extends AbstractNonAuthentifiedView { export default class extends AbstractAuthentifiedView {
constructor(params) { constructor(params) {
super(params, "Register", "/home"); super(params, "Register", "/home");
} }

View File

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

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

@ -10,10 +10,11 @@
<body> <body>
<nav class="nav"> <nav class="nav">
<a href="/" class="nav__link" data-link>Dashboard</a> <a href="/" class="nav__link" data-link>Dashboard</a>
<a href="/profiles" class="nav__link" data-link>Profiles</a> <a href="/posts" class="nav__link" data-link>Posts</a>
<a href="/settings" class="nav__link" data-link>Settings</a>
<a href="/login" class="nav__link" data-link>Login</a> <a href="/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

@ -3,15 +3,11 @@ from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver 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. # 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) title = models.CharField(max_length=40)
@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

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

Before

Width:  |  Height:  |  Size: 35 KiB

1
profiles/status_code.py Normal file
View File

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

View File

@ -1,11 +1,7 @@
from django.urls import path from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from . import viewsets from . import views
urlpatterns = [ urlpatterns = [
path("<int:pk>", viewsets.ProfileViewSet.as_view({'get': 'retrieve', 'patch': 'partial_update'}), name="profile_page"), path("<int:pk>", views.ProfileView.as_view(), 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")

19
profiles/views.py Normal file
View File

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

View File

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

View File

@ -10,9 +10,7 @@ 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
@ -22,8 +20,7 @@ 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

@ -10,8 +10,6 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/ https://docs.djangoproject.com/en/4.2/ref/settings/
""" """
import os
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -27,12 +25,12 @@ 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! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
CSRF_TRUSTED_ORIGINS = ['https://code.chauvet.pro', 'https://django.chauvet.pro']
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_ALLOW_ALL = False
CSRF_TRUSTED_ORIGINS = ["https://django.chauvet.pro"]
CORS_ORIGIN_WHITELIST = ( CORS_ORIGIN_WHITELIST = (
'http://localhost:8000', 'http://localhost:8000',
) )
@ -43,8 +41,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',