Compare commits

..

17 Commits

Author SHA1 Message Date
651fbbd67f css patch 2023-12-18 22:18:49 +01:00
1961047703 merge with Chatte 2023-12-18 21:29:35 +01:00
929c1fdeb3 merge with Chatte 2023-12-18 21:27:52 +01:00
980a66fb47 fix: client.me 2023-12-18 21:27:10 +01:00
ceed7c2c4a fix: profile page 2 2023-12-17 20:29:04 +01:00
6a537a9b68 fix: profile work 2023-12-17 20:27:13 +01:00
26a9152756 fix: register view 2023-12-17 20:17:52 +01:00
f32d38287a tentative de merge 2023-12-16 18:02:07 +01:00
51354d9922 tentative de merge 2023-12-16 18:00:38 +01:00
b5d73e59fd core: simplified login and register, fix: me 2023-12-16 17:41:31 +01:00
86e2528d04 merge with Chatte 2023-12-16 17:01:03 +01:00
1f41e62a86 core: refonte profile 2023-12-16 16:42:30 +01:00
51059e6bf3 merge with Chatte 2023-12-16 16:15:13 +01:00
115ae9357a fix: profile not found GET return error 2023-12-16 11:06:24 +01:00
1b44d5c94f Merge remote-tracking branch 'refs/remotes/origin/server' into server 2023-12-16 10:58:11 +01:00
468a3917a9 fix css file to /login /register 2023-12-16 10:39:07 +01:00
9be65a2bab Update README.md 2023-12-16 10:35:52 +01:00
18 changed files with 189 additions and 163 deletions

View File

@ -24,7 +24,7 @@ pip install -r requirements.txt
``` ```
python manage.py makemigrations games python manage.py makemigrations games
python manage.py makemigrations profiles python manage.py makemigrations profiles
python manage.py runserver makemigrations chat python manage.py makemigrations chat
python manage.py migrate python manage.py migrate
``` ```
- Start the developpement server - Start the developpement server

View File

@ -1,7 +1,7 @@
body { body {
margin: 20px; margin: 0.5em;
font-family: 'Quicksand', sans-serif; font-family: 'Quicksand', sans-serif;
font-size: 60px; font-size: 40px;
} }
a { a {

View File

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

View File

@ -45,7 +45,7 @@
} }
#app #messages { #app #messages {
max-height: 50em; max-height: 40em;
overflow: scroll; overflow: scroll;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;

View File

@ -0,0 +1,19 @@
import { Profile } from "./profile.js";
class MyProfile extends Profile
{
async change_avatar(form_data)
{
let response = await this.client._patch_file(`/api/profiles/me`, form_data);
let response_data = await response.json()
return response_data;
}
async init()
{
super.init("me");
}
}
export {MyProfile}

View File

@ -1,7 +1,15 @@
import { Client } from "./client.js";
class Account class Account
{ {
/**
* @param {Client} client
*/
constructor (client) constructor (client)
{ {
/**
* @type {Client} client
*/
this.client = client; this.client = client;
} }
@ -28,7 +36,6 @@ class Account
this.client._logged = false; this.client._logged = false;
return null; return null;
} }
console.log(response_data)
if (response_data == "user deleted") if (response_data == "user deleted")
this.client._logged = false; this.client._logged = false;
return response_data; return response_data;
@ -55,7 +62,7 @@ class Account
if (JSON.stringify(response_data) == JSON.stringify({'detail': 'Authentication credentials were not provided.'})) if (JSON.stringify(response_data) == JSON.stringify({'detail': 'Authentication credentials were not provided.'}))
{ {
this.client._logged = false; this.client._;
return null; return null;
} }
return response_data; return response_data;

View File

@ -1,8 +1,8 @@
import { Account } from "./account.js"; import { Account } from "./account.js";
import { MatchMaking } from "./matchmaking.js"; import { MatchMaking } from "./matchmaking.js";
import { Profile } from "./profile.js";
import { Profiles } from "./profiles.js"; import { Profiles } from "./profiles.js";
import { Channels } from './chat/channels.js'; import { Channels } from './chat/channels.js';
import { MyProfile } from "./MyProfile.js";
function getCookie(name) function getCookie(name)
{ {
@ -94,15 +94,23 @@ class Client
return response; return response;
} }
async _update_logged(state)
{
if (!this.logged && state)
{
this.me = new MyProfile(this);
await this.me.init();
}
this.logged = state;
}
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.id != undefined)
{ {
this.me = new Profile(this) await this._update_logged(true);
await this.me.init(data.id)
this.logged = true;
return null; return null;
} }
return data; return data;
@ -111,7 +119,7 @@ class Client
async logout() async logout()
{ {
await this._get("/api/accounts/logout"); await this._get("/api/accounts/logout");
this.logged = false; await this._update_logged(false);
} }
async _test_logged() async _test_logged()
@ -120,10 +128,7 @@ class Client
let data = await response.json(); let data = await response.json();
if (data.id !== undefined) if (data.id !== undefined)
{ await this._update_logged(true);
this.me = new Profile(this)
await this.me.init(data.id)
}
return data.id !== undefined; return data.id !== undefined;
} }
} }

View File

@ -1,9 +1,9 @@
import { client, navigateTo } from "../index.js" import { Client } from "./client.js";
class MatchMaking class MatchMaking
{ {
/** /**
* @param {client} client * @param {Client} client
*/ */
constructor(client) constructor(client)
{ {

View File

@ -1,7 +1,15 @@
import { Client } from "./client.js";
class Profile class Profile
{ {
/**
* @param {Client} client
*/
constructor (client, username = undefined, avatar_url = undefined, user_id = undefined) constructor (client, username = undefined, avatar_url = undefined, user_id = undefined)
{ {
/**
* @type {Client} client
*/
this.client = client; this.client = client;
this.username = username; this.username = username;
this.avatar_url = avatar_url this.avatar_url = avatar_url
@ -13,23 +21,10 @@ class Profile
let response = await this.client._get(`/api/profiles/${user_id}`); let response = await this.client._get(`/api/profiles/${user_id}`);
let response_data = await response.json(); let response_data = await response.json();
this.user_id = user_id; this.user_id = response.user_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)
{
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} export {Profile}

View File

@ -2,8 +2,14 @@ import { Profile } from "./profile.js";
class Profiles class Profiles
{ {
/**
* @param {Client} client
*/
constructor (client) constructor (client)
{ {
/**
* @type {Client} client
*/
this.client = client this.client = client
} }

View File

@ -79,6 +79,7 @@ const router = async (uri) => {
lastView = view; lastView = view;
await client.isAuthentificate();
let content = await view.getHtml(); let content = await view.getHtml();
if (content == null) if (content == null)
return 1; return 1;

View File

@ -0,0 +1,18 @@
function clear(property_name, elements_id)
{
elements_id.forEach(element_id => {
let element = document.getElementById(element_id)
element[property_name] = ""
});
}
function fill_errors(errors, property_name)
{
Object.keys(errors).forEach(error_field =>
{
let element = document.getElementById(error_field);
element[property_name] = errors[error_field];
});
}
export {fill_errors, clear}

View File

@ -1,4 +1,5 @@
import { client, navigateTo } from "../index.js"; import { client, navigateTo } from "../index.js";
import { clear, fill_errors } from "../utils/formUtils.js";
import AbstractAuthentificateView from "./AbstractAuthentifiedView.js"; import AbstractAuthentificateView from "./AbstractAuthentifiedView.js";
export default class extends AbstractAuthentificateView export default class extends AbstractAuthentificateView
@ -10,63 +11,39 @@ export default class extends AbstractAuthentificateView
async postInit() async postInit()
{ {
if (this.fill() === null) document.getElementById("save-account-button").onclick = this.save_account;
return; document.getElementById("delete-account-button").onclick = this.delete_account;
document.getElementById("save-account-button").onclick = this.acccount_save; document.getElementById("save-profile-button").onclick = this.save_profile;
document.getElementById("delete-account-button").onclick = this.account_delete_accounts;
} }
async fill() async delete_account()
{ {
let data = await client.account.get(); let current_password = document.getElementById("current_password-input").value;
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); let response_data = await client.account.delete(current_password);
if (response_data === null) console.log(await client.isAuthentificate())
if (response_data === null || response_data === "user deleted")
{ {
navigateTo("/login"); navigateTo("/login");
return; return;
} }
clear("innerHTML", ["current_password-input"])
["delete", "current_password"].forEach(error_field => { fill_errors({"current_password-input": response_data["password"]}, "innerHTML")
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_account()
}
async save()
{ {
let username = document.getElementById("username").value; let username = document.getElementById("username-input").value;
let new_password = document.getElementById("new_password").value; let new_password = document.getElementById("new_password-input").value;
let current_password = document.getElementById("current_password").value; let current_password = document.getElementById("current_password-input").value;
let data = {}; let data = {};
data.username = username; data.username = username;
if (new_password.length != 0) if (new_password.length != 0)
data.new_password = new_password; data.new_password = new_password;
let response_data = await client.account.update(data, current_password); let response_data = await client.account.update(data, current_password);
if (response_data === null) if (response_data === null)
@ -74,58 +51,55 @@ export default class extends AbstractAuthentificateView
navigateTo("/login"); navigateTo("/login");
return; return;
} }
else if (response_data === "data has been alterate")
{ if (response_data === "data has been alterate")
navigateTo("/me"); response_data = {"save-account": "saved"}
return;
clear("innerHTML", ["username", "new_password", "current_password", "save-account", "delete-account"])
fill_errors(response_data, "innerHTML")
} }
["username", "new_password", "current_password"].forEach(error_field => { async save_profile()
let error_display = document.getElementById(`error_${error_field}`); {
if (error_display != null) let avatar = document.getElementById("avatar-input");
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) if (avatar.files[0] !== undefined)
{ {
let form_data = new FormData(); let form_data = new FormData();
form_data.append("file", avatar.files[0]); form_data.append("file", avatar.files[0]);
await client.me.change_avatar(form_data) await client.me.change_avatar(form_data);
} }
document.getElementById("save-profile").innerHTML = "Saved";
} }
async getHtml() async getHtml()
{ {
return ` return `
<link rel="stylesheet" href="static/css/me.css"> <link rel="stylesheet" href="/static/css/me.css">
<h1>ME</h1> <h1>ME</h1>
<div id="main">
<div class="account"> <div class="account">
<h3>Account</h3> <h3>Account</h3>
<input type="text" placeholder="username" id="username"> <input type="text" placeholder="username" id="username-input" text=${client.me.username}>
<span id="error_username"></span> <span id="username"></span>
<input type=password placeholder="new password" id="new_password"> <input type=password placeholder="new_password" id="new_password-input">
<span id="error_new_password"></span> <span id="new_password"></span>
<input type=password placeholder="current password" id="current_password"> <input type=password placeholder="current_password" id="current_password-input">
<span id="error_current_password"></span> <span id="current_password"></span>
<input type="button" value="Save Credentials" id="save-account-button"> <input type="button" value="Save Credentials" id="save-account-button">
<span id="error_save"></span> <span id="save-account"></span>
<input type="button" value="Delete Account" id="delete-account-button"> <input type="button" value="Delete Account" id="delete-account-button">
<span id="error_delete"></span> <span id="delete-account"></span>
</div> </div>
<div class="profile"> <div class="profile">
<h3>Profile</h3> <h3>Profile</h3>
<input type="file" id="avatar" accept="image/png, image/jpeg"> <input type="file" id="avatar-input" accept="image/png, image/jpeg">
<input type="button" value="Save profile" id="save-profile-button"> <input type="button" value="Save profile" id="save-profile-button">
<span id="error_save"></span> <span id="save-profile"></span>
</div> </div>
<a href="/logout" class="nav__link" data-link>Logout</a> <a href="/logout" class="nav__link" data-link>Logout</a>
</div>
`; `;
} }
} }

View File

@ -52,6 +52,7 @@ export default class extends AbstractView {
// chat // chat
if (logged && client.me.user_id != user.user_id) { if (logged && client.me.user_id != user.user_id) {
let add_chat = document.createElement("a"); let add_chat = document.createElement("a");
add_chat.href = "";
add_chat.id = "add_chat_off"; add_chat.id = "add_chat_off";
add_chat.onclick = async () => { add_chat.onclick = async () => {
if (client.channel != undefined) { if (client.channel != undefined) {

View File

@ -1,10 +1,11 @@
import { client, navigateTo } from "../../index.js"; import { client, navigateTo } from "../../index.js";
import { clear, fill_errors } from "../../utils/formUtils.js";
import AbstractNonAuthentifiedView from "../AbstractNonAuthentified.js"; import AbstractNonAuthentifiedView from "../AbstractNonAuthentified.js";
async function login() async function login()
{ {
let username = document.getElementById("username").value; let username = document.getElementById("username-input").value;
let password = document.getElementById("password").value; let password = document.getElementById("password-input").value;
let response_data = await client.login(username, password); let response_data = await client.login(username, password);
@ -14,17 +15,8 @@ async function login()
return; return;
} }
["username", "user", "password"].forEach(error_field => { clear("innerHTML", ["username", "user", "password"]);
let error_display = document.getElementById(`error_${error_field}`); fill_errors(response_data, "innerHTML");
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 { export default class extends AbstractNonAuthentifiedView {
@ -34,20 +26,20 @@ export default class extends AbstractNonAuthentifiedView {
async postInit() async postInit()
{ {
document.getElementById("button").onclick = login; document.getElementById("login-button").onclick = login;
} }
async getHtml() { async getHtml() {
return ` return `
<div class=form> <div class=form>
<label>Login</label> <label>Login</label>
<link rel="stylesheet" href="static/css/accounts/login.css"> <link rel="stylesheet" href="/static/css/accounts/login.css">
<input type="text" id="username" placeholder="username"> <input type="text" id="username-input" placeholder="username">
<span id="error_username"></span> <span id="username"></span>
<input type="password" id="password" placeholder="password"> <input type="password" id="password-input" placeholder="password">
<span id="error_password"></span> <span id="password"></span>
<input type="button" value="login" id="button"> <input type="button" value="Login" id="login-button">
<span id="error_user"></span> <span id="user"></span>
<a href="/register" class="nav__link" data-link>Register</a> <a href="/register" class="nav__link" data-link>Register</a>
</div> </div>
`; `;

View File

@ -1,10 +1,11 @@
import { client, navigateTo } from "../../index.js"; import { client, navigateTo } from "../../index.js";
import { clear, fill_errors } from "../../utils/formUtils.js";
import AbstractNonAuthentifiedView from "../AbstractNonAuthentified.js"; import AbstractNonAuthentifiedView from "../AbstractNonAuthentified.js";
async function register() async function register()
{ {
let username = document.getElementById("username").value; let username = document.getElementById("username-input").value;
let password = document.getElementById("password").value; let password = document.getElementById("password-input").value;
let response_data = await client.account.create(username, password); let response_data = await client.account.create(username, password);
@ -14,17 +15,8 @@ async function register()
return; return;
} }
["username", "user", "password"].forEach(error_field => { clear("innerHTML", ["username", "user", "password"]);
let error_display = document.getElementById(`error_${error_field}`); fill_errors(response_data, "innerHTML");
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 { export default class extends AbstractNonAuthentifiedView {
@ -34,20 +26,20 @@ export default class extends AbstractNonAuthentifiedView {
async postInit() async postInit()
{ {
document.getElementById("button").onclick = register; document.getElementById("register-button").onclick = register;
} }
async getHtml() { async getHtml() {
return ` return `
<div class=form> <div class=form>
<label>Register</label> <label>Register</label>
<link rel="stylesheet" href="static/css/accounts/register.css"> <link rel="stylesheet" href="/static/css/accounts/register.css">
<input type="text" id="username" placeholder="username"> <input type="text" id="username-input" placeholder="username">
<span id="error_username"></span> <span id="username"></span>
<input type="password" id="password" placeholder="password"> <input type="password" id="password-input" placeholder="password">
<span id="error_password"></span> <span id="password"></span>
<input type="button" value="register" id="button"> <input type="button" value="Register" id="register-button">
<span id="error_user"></span> <span id="user"></span>
<a href="/login" class="nav__link" data-link>Login</a> <a href="/login" class="nav__link" data-link>Login</a>
</div> </div>
`; `;

View File

@ -5,7 +5,7 @@ from django.conf.urls.static import static
from . import viewsets from . import viewsets
urlpatterns = [ urlpatterns = [
path("<int:pk>", viewsets.ProfileViewSet.as_view({'get': 'retrieve', 'patch': 'partial_update'}), name="profile_page"), path("me", viewsets.MyProfileViewSet.as_view({'patch': 'partial_update', 'get': 'retrieve'}), name="my_profile_page"),
path("<int:pk>", viewsets.ProfileViewSet.as_view({'get': 'retrieve'}), name="profile_page"),
path("", viewsets.ProfileViewSet.as_view({'get': 'list'}), name="profiles_list"), 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") ] + static("/static/avatars/", document_root="./avatars")

View File

@ -3,6 +3,7 @@ from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework import permissions, status from rest_framework import permissions, status
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.authentication import SessionAuthentication
from django.http import HttpRequest from django.http import HttpRequest
from django.db.models import QuerySet from django.db.models import QuerySet
@ -17,7 +18,9 @@ class ProfileViewSet(viewsets.ModelViewSet):
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) if (not self.queryset().filter(pk=pk).exists()):
return Response({"detail": "Profile not found."}, status=status.HTTP_404_NOT_FOUND)
instance = self.queryset().get(pk=pk)
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)
@ -31,14 +34,28 @@ class ProfileViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(user=self.request.user) serializer.save(user=self.request.user)
def perform_update(self, serializer): class MyProfileViewSet(viewsets.ModelViewSet):
query: QuerySet = ProfileModel.objects.filter(pk=self.request.user.pk)
if (not query.exists()): permission_classes = (permissions.IsAuthenticated,)
return Response("profile not found", status=status.HTTP_400_BAD_REQUEST) authentication_classes = (SessionAuthentication,)
profile: ProfileModel = ProfileModel.objects.get(pk=self.request.user.pk) serializer_class = ProfileSerializer
queryset = ProfileModel.objects.all
def get_object(self):
obj = self.queryset().get(pk=self.request.user.pk)
return obj
def perform_update(self, serializer, pk=None):
profile: ProfileModel = self.get_object()
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 != "./profiles/static/avatars/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()
def retrieve(self, request: HttpRequest, pk=None):
instance: ProfileModel = self.get_object()
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)