From 0e946b3bb80ede814cfb0b7f44b4af6f48410c4e Mon Sep 17 00:00:00 2001 From: starnakin Date: Mon, 18 Dec 2023 23:18:39 +0100 Subject: [PATCH 01/42] fix: gameModel.create is not cringe now --- games/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/games/models.py b/games/models.py index a2d8315..9a23ad9 100644 --- a/games/models.py +++ b/games/models.py @@ -6,7 +6,7 @@ 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) + GameMembersModel(game_id=self.pk, member_id=user_id).save() return self.pk class GameMembersModel(models.Model): From 4f0b2250bd9dce626d0cdf86bd89cca3ccee2537 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 19:10:26 +0100 Subject: [PATCH 02/42] fix --- frontend/static/js/api/account.js | 2 +- frontend/static/js/api/matchmaking.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/static/js/api/account.js b/frontend/static/js/api/account.js index db22a63..3e44d0e 100644 --- a/frontend/static/js/api/account.js +++ b/frontend/static/js/api/account.js @@ -33,7 +33,7 @@ class Account if (JSON.stringify(response_data) == JSON.stringify({'detail': 'Authentication credentials were not provided.'})) { - this.client._logged = false; + this.client._update_logged(false); return null; } if (response_data == "user deleted") diff --git a/frontend/static/js/api/matchmaking.js b/frontend/static/js/api/matchmaking.js index 6943077..ce0adc8 100644 --- a/frontend/static/js/api/matchmaking.js +++ b/frontend/static/js/api/matchmaking.js @@ -8,7 +8,7 @@ class MatchMaking constructor(client) { /** - * @type {client} + * @type {Client} */ this.client = client } From 8ed9deab2d58e07f8f99b4b5292e153956a32f21 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 19:11:56 +0100 Subject: [PATCH 03/42] add: attribute game state to gamemodel --- games/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/games/models.py b/games/models.py index 9a23ad9..3c95a6b 100644 --- a/games/models.py +++ b/games/models.py @@ -2,12 +2,19 @@ from django.db import models # Create your models here. class GameModel(models.Model): + + finished = models.BooleanField(default=False) + started = models.BooleanField(default=False) + winner_id = models.IntegerField() def create(self, users_id: [int]): self.save() for user_id in users_id: GameMembersModel(game_id=self.pk, member_id=user_id).save() return self.pk + + def get_users(self): + return list(GameMembersModel.objects.filter(self.pk)) class GameMembersModel(models.Model): game_id = models.IntegerField() From bfcfabb35ab5a710a9a9c134f768cd6053b85ff2 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 19:15:47 +0100 Subject: [PATCH 04/42] init: tournament --- README.md | 1 + .../static/js/api/tournament/tournament.js | 19 ++++++ .../static/js/api/tournament/tournaments.js | 33 ++++++++++ tournament/__init__.py | 0 tournament/admin.py | 3 + tournament/apps.py | 6 ++ tournament/models.py | 47 ++++++++++++++ tournament/tests.py | 3 + tournament/urls.py | 10 +++ tournament/views.py | 61 +++++++++++++++++++ trancendence/settings.py | 1 + trancendence/urls.py | 1 + 12 files changed, 185 insertions(+) create mode 100644 frontend/static/js/api/tournament/tournament.js create mode 100644 frontend/static/js/api/tournament/tournaments.js create mode 100644 tournament/__init__.py create mode 100644 tournament/admin.py create mode 100644 tournament/apps.py create mode 100644 tournament/models.py create mode 100644 tournament/tests.py create mode 100644 tournament/urls.py create mode 100644 tournament/views.py diff --git a/README.md b/README.md index 255ae1f..d96ff83 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ pip install -r requirements.txt python manage.py makemigrations games python manage.py makemigrations profiles python manage.py makemigrations chat +python manage.py makemigrations tournament python manage.py migrate ``` - Start the developpement server diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js new file mode 100644 index 0000000..7ecce9c --- /dev/null +++ b/frontend/static/js/api/tournament/tournament.js @@ -0,0 +1,19 @@ +import { Client } from "./client.js"; + +class Tourmanent +{ + /** + * @param {Client} client + */ + constructor(client) + { + /** + * @type {Client} + */ + this.client = client + } + + +} + +export { Tourmanent } \ No newline at end of file diff --git a/frontend/static/js/api/tournament/tournaments.js b/frontend/static/js/api/tournament/tournaments.js new file mode 100644 index 0000000..60a92a7 --- /dev/null +++ b/frontend/static/js/api/tournament/tournaments.js @@ -0,0 +1,33 @@ +import { Client } from "./client.js"; + +class Tourmanents +{ + /** + * @param {Client} client + */ + constructor(client) + { + /** + * @type {Client} + */ + this.client = client + } + + async createTournament(nb_players, online) + { + if (online) + return "offline"; + let response = await this.client._post("/api/tournaments/", {numbers_of_players: nb_players}); + let response_data = await response.json(); + + if (JSON.stringify(response_data) === JSON.stringify({'detail': 'Authentication credentials were not provided.'})) + { + this.client._update_logged(false); + return null; + } + return response_data.tournament_id; + } + +} + +export { Tourmanents } \ No newline at end of file diff --git a/tournament/__init__.py b/tournament/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tournament/admin.py b/tournament/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/tournament/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/tournament/apps.py b/tournament/apps.py new file mode 100644 index 0000000..15ea9fb --- /dev/null +++ b/tournament/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TournamentConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'tournament' diff --git a/tournament/models.py b/tournament/models.py new file mode 100644 index 0000000..7b4ce71 --- /dev/null +++ b/tournament/models.py @@ -0,0 +1,47 @@ +from django.db import models + +from games.models import GameModel + +# Create your models here.tu +class TournamentModel(models.Model): + + name = models.CharField() + nb_players = models.IntegerField() + nb_players_by_game = models.IntegerField() + level = models.IntegerField() + started = models.BooleanField(default=False) + finished = models.BooleanField(default=False) + + def create(self, nb_players: int, nb_players_by_game: int, name: str = ""): + self.level = 1 + number: int = nb_players + while (number != nb_players_by_game): + number = number // 2 + (number % 2) + self.level += 1 + self.nb_players = nb_players + self.nb_players_by_game = nb_players_by_game + self.name = name + return self.save() + + def create_game(self, users_id): + game_id = GameModel.create(users_id=users_id) + TournamentGamesModel(game_id=game_id, tournament_id=self.pk).save() + return game_id + + def get_games_id_by_level(self, level): + return list(TournamentGamesModel.objects.filter(tournament_id=self.pk, tournament_level=level)) + + def get_games_id(self): + return list(TournamentGamesModel.objects.filter(tournament_id=self.pk)) + + def get_players_id(self): + lst: [int] = [] + for game_id in self.get_games_id(): + lst.append(GameMembersModel.objects.filter(game_id=game_id)) + return lst + +class TournamentGamesModel(models.Model): + + tournament_id = models.IntegerField() + tournament_level = models.IntegerField() + game_id = models.IntegerField() \ No newline at end of file diff --git a/tournament/tests.py b/tournament/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/tournament/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/tournament/urls.py b/tournament/urls.py new file mode 100644 index 0000000..4451812 --- /dev/null +++ b/tournament/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from django.conf import settings +from django.conf.urls.static import static + +from .views import TournamentsView, TournamentView + +urlpatterns = [ + path("", name="tournament_page"), + path("", TournamentsView.as_view(), name="tournaments"), +] \ No newline at end of file diff --git a/tournament/views.py b/tournament/views.py new file mode 100644 index 0000000..f055d22 --- /dev/null +++ b/tournament/views.py @@ -0,0 +1,61 @@ +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import permissions, status +from rest_framework.authentication import SessionAuthentication + +from django.http import HttpRequest +from django.contrib.auth import login + +from matchmaking.models import in_matchmaking +from .models import TournamentModel + +# Create your views here. +class TournamentsView(APIView): + + permission_classes = (permissions.IsAuthenticated,) + authentication_classes = (SessionAuthentication,) + + def post(self, request: HttpRequest): + + data: dict = request.data + + nb_players_by_game = data.get("nb_players_by_game") + if (nb_players_by_game is None): + return Response("nb_player_by_game is required.", status=status.HTTP_400_BAD_REQUEST) + + nb_players = data.get("nb_players") + if (nb_players is None): + return Response("nb_players is required.", status=status.HTTP_400_BAD_REQUEST) + + tournament_id: int = TournamentModel.create(users_id=users_id, nb_players=nb_players) + + return Response({"tournament_id": tournament_id}, status=status.HTTP_201_CREATED) + +class TournamentView(APIView): + + permission_classes = (permissions.IsAuthenticated,) + authentication_classes = (SessionAuthentication,) + + def get(self, request: HttpRequest, pk): + + if (not TournamentModel.objects.filter(pk=pk).exists()): + return Response({"detail": "Tournament not found."}, status=status.HTTP_404_NOT_FOUND) + + tournament = TournamentModel.objects.get(pk=pk) + + levels: [[int]] = [] + level: [int] = tournament.get_games_id_by_level + while level != []: + levels.append(level) + level: [int] = tournament.get_games_id_by_level + + data = { + "name": tournament.name, + "finished": tournament.finished, + "started": tournament.finished, + "nb_players": tournament.nb_players, + "nb_players_by_game": tournament.nb_players_by_game, + "levels": levels + } + + return Response(data, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/trancendence/settings.py b/trancendence/settings.py index 411aca1..3080286 100644 --- a/trancendence/settings.py +++ b/trancendence/settings.py @@ -43,6 +43,7 @@ INSTALLED_APPS = [ 'channels', 'daphne', + 'tournament.apps.TournamentConfig', 'matchmaking.apps.MatchmakingConfig', 'games.apps.GamesConfig', 'accounts.apps.AccountsConfig', diff --git a/trancendence/urls.py b/trancendence/urls.py index 46a790f..0c9cd6d 100644 --- a/trancendence/urls.py +++ b/trancendence/urls.py @@ -22,5 +22,6 @@ urlpatterns = [ path('api/profiles/', include('profiles.urls')), path('api/accounts/', include('accounts.urls')), path('api/chat/', include('chat.urls')), + path('api/tournament', include('tournament.urls')), path('', include('frontend.urls')), ] From 1032a8fd986db20bda7e994af63d8b419181ab1a Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 21:09:31 +0100 Subject: [PATCH 05/42] core: recreation of tournament view by using queryset --- tournament/serializers.py | 19 +++++++++++ tournament/urls.py | 8 ++--- tournament/{views.py => viewset.py} | 50 ++++++++++++++--------------- 3 files changed, 47 insertions(+), 30 deletions(-) create mode 100644 tournament/serializers.py rename tournament/{views.py => viewset.py} (51%) diff --git a/tournament/serializers.py b/tournament/serializers.py new file mode 100644 index 0000000..218eef5 --- /dev/null +++ b/tournament/serializers.py @@ -0,0 +1,19 @@ +from rest_framework import serializers +from .models import TournamentModel + +class TournamentSerializer(serializers.ModelSerializer): + + levels = serializers.SerializerMethodField() + + class Meta: + model = TournamentModel + fields = ["name", "nb_players", "nb_players_by_game", "level", "started", "finished", "levels"] + + def get_levels(self, instance): + levels: [[int]] = [] + for i in range(instance.level): + level: [int] = instance.get_games_id_by_level(i) + if (level == []): + break + levels.append(level) + return levels diff --git a/tournament/urls.py b/tournament/urls.py index 4451812..f614391 100644 --- a/tournament/urls.py +++ b/tournament/urls.py @@ -1,10 +1,10 @@ -from django.urls import path +from django.urls import path, re_path from django.conf import settings from django.conf.urls.static import static -from .views import TournamentsView, TournamentView +from .viewset import TournamentViewSet urlpatterns = [ - path("", name="tournament_page"), - path("", TournamentsView.as_view(), name="tournaments"), + path("", TournamentViewSet.as_view({"get": "retrieve", "post": "create"}), name="tournament_page"), + re_path(r"()?", TournamentViewSet.as_view({"get": "list"}), name="tournaments"), ] \ No newline at end of file diff --git a/tournament/views.py b/tournament/viewset.py similarity index 51% rename from tournament/views.py rename to tournament/viewset.py index f055d22..0d7f585 100644 --- a/tournament/views.py +++ b/tournament/viewset.py @@ -1,21 +1,25 @@ -from rest_framework.views import APIView +from rest_framework import viewsets from rest_framework.response import Response from rest_framework import permissions, status from rest_framework.authentication import SessionAuthentication from django.http import HttpRequest from django.contrib.auth import login +from django.db.models import QuerySet from matchmaking.models import in_matchmaking from .models import TournamentModel +from .serializers import TournamentSerializer # Create your views here. -class TournamentsView(APIView): +class TournamentViewSet(viewsets.ModelViewSet): + queryset = TournamentModel.objects.all + serializer_class = TournamentSerializer permission_classes = (permissions.IsAuthenticated,) authentication_classes = (SessionAuthentication,) - def post(self, request: HttpRequest): + def perform_create(self, request: HttpRequest): data: dict = request.data @@ -27,35 +31,29 @@ class TournamentsView(APIView): if (nb_players is None): return Response("nb_players is required.", status=status.HTTP_400_BAD_REQUEST) - tournament_id: int = TournamentModel.create(users_id=users_id, nb_players=nb_players) + tournament_id: int = TournamentModel().create(nb_players=nb_players, nb_players_by_game=nb_players_by_game) return Response({"tournament_id": tournament_id}, status=status.HTTP_201_CREATED) + + def list(self, request: HttpRequest, state: str = ""): + query: QuerySet + match state: + case "started": + query = TournamentModel.objects.filter(started=True, finished=False) + case "finished": + query = TournamentModel.objects.filter(finished=True) + case "currently": + query = TournamentModel.objects.filter(started=False, finished=False) + case _: + query = TournamentModel.objects.all() + serializer = TournamentSerializer(query, many=True) + return Response(serializer.data) -class TournamentView(APIView): - - permission_classes = (permissions.IsAuthenticated,) - authentication_classes = (SessionAuthentication,) - - def get(self, request: HttpRequest, pk): + def retrieve(self, request: HttpRequest, pk): if (not TournamentModel.objects.filter(pk=pk).exists()): return Response({"detail": "Tournament not found."}, status=status.HTTP_404_NOT_FOUND) tournament = TournamentModel.objects.get(pk=pk) - levels: [[int]] = [] - level: [int] = tournament.get_games_id_by_level - while level != []: - levels.append(level) - level: [int] = tournament.get_games_id_by_level - - data = { - "name": tournament.name, - "finished": tournament.finished, - "started": tournament.finished, - "nb_players": tournament.nb_players, - "nb_players_by_game": tournament.nb_players_by_game, - "levels": levels - } - - return Response(data, status=status.HTTP_200_OK) \ No newline at end of file + return Response(self.serializer_class(tournament).data, status=status.HTTP_200_OK) \ No newline at end of file From adf0c5ceeddb4bbaee3b4a8ede0df6e88a6c867d Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 21:10:12 +0100 Subject: [PATCH 06/42] core: change tournament api url --- trancendence/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trancendence/urls.py b/trancendence/urls.py index 0c9cd6d..38eb6d0 100644 --- a/trancendence/urls.py +++ b/trancendence/urls.py @@ -22,6 +22,6 @@ urlpatterns = [ path('api/profiles/', include('profiles.urls')), path('api/accounts/', include('accounts.urls')), path('api/chat/', include('chat.urls')), - path('api/tournament', include('tournament.urls')), + path('api/tournaments/', include('tournament.urls')), path('', include('frontend.urls')), ] From 288252ab379a8413e712fa8d26138bc64fed7783 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 21:10:24 +0100 Subject: [PATCH 07/42] remove: useless function --- profiles/viewsets.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/profiles/viewsets.py b/profiles/viewsets.py index 445e2c7..6e73ad8 100644 --- a/profiles/viewsets.py +++ b/profiles/viewsets.py @@ -31,9 +31,6 @@ class ProfileViewSet(viewsets.ModelViewSet): 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) - class MyProfileViewSet(viewsets.ModelViewSet): permission_classes = (permissions.IsAuthenticated,) From 56cfd563d77c42781cdb83e3c51f2e8271013875 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 21:11:37 +0100 Subject: [PATCH 08/42] fix: model, limite length of tournament.name and winner_id can be null now --- games/models.py | 2 +- tournament/models.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/games/models.py b/games/models.py index 3c95a6b..0493dc5 100644 --- a/games/models.py +++ b/games/models.py @@ -5,7 +5,7 @@ class GameModel(models.Model): finished = models.BooleanField(default=False) started = models.BooleanField(default=False) - winner_id = models.IntegerField() + winner_id = models.IntegerField(null=True, blank=True) def create(self, users_id: [int]): self.save() diff --git a/tournament/models.py b/tournament/models.py index 7b4ce71..5d2ae37 100644 --- a/tournament/models.py +++ b/tournament/models.py @@ -5,7 +5,7 @@ from games.models import GameModel # Create your models here.tu class TournamentModel(models.Model): - name = models.CharField() + name = models.CharField(max_length=100) nb_players = models.IntegerField() nb_players_by_game = models.IntegerField() level = models.IntegerField() @@ -21,7 +21,9 @@ class TournamentModel(models.Model): self.nb_players = nb_players self.nb_players_by_game = nb_players_by_game self.name = name - return self.save() + self.save() + + return self.pk def create_game(self, users_id): game_id = GameModel.create(users_id=users_id) From 9523ac45542596a9b44fb165b4b58aecc2f078fa Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 21:57:16 +0100 Subject: [PATCH 09/42] change url patern to tournament --- tournament/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tournament/urls.py b/tournament/urls.py index f614391..47abf9b 100644 --- a/tournament/urls.py +++ b/tournament/urls.py @@ -5,6 +5,6 @@ from django.conf.urls.static import static from .viewset import TournamentViewSet urlpatterns = [ - path("", TournamentViewSet.as_view({"get": "retrieve", "post": "create"}), name="tournament_page"), - re_path(r"()?", TournamentViewSet.as_view({"get": "list"}), name="tournaments"), + path("", TournamentViewSet.as_view({"get": "retrieve"}), name="tournament_page"), + re_path(r"search/(?P\w*)", TournamentViewSet.as_view({"get": "list", "post": "create"}), name="tournaments"), ] \ No newline at end of file From 9bdd07d59e0274498d9afeef91cf2e4389faa1ec Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 21:58:52 +0100 Subject: [PATCH 10/42] change tounament: viewset, serializer, models simply the code and use django feat --- tournament/models.py | 13 ------------- tournament/serializers.py | 7 +++++-- tournament/viewset.py | 24 +++++++++++------------- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/tournament/models.py b/tournament/models.py index 5d2ae37..287c929 100644 --- a/tournament/models.py +++ b/tournament/models.py @@ -12,19 +12,6 @@ class TournamentModel(models.Model): started = models.BooleanField(default=False) finished = models.BooleanField(default=False) - def create(self, nb_players: int, nb_players_by_game: int, name: str = ""): - self.level = 1 - number: int = nb_players - while (number != nb_players_by_game): - number = number // 2 + (number % 2) - self.level += 1 - self.nb_players = nb_players - self.nb_players_by_game = nb_players_by_game - self.name = name - self.save() - - return self.pk - def create_game(self, users_id): game_id = GameModel.create(users_id=users_id) TournamentGamesModel(game_id=game_id, tournament_id=self.pk).save() diff --git a/tournament/serializers.py b/tournament/serializers.py index 218eef5..cb8054f 100644 --- a/tournament/serializers.py +++ b/tournament/serializers.py @@ -3,11 +3,14 @@ from .models import TournamentModel class TournamentSerializer(serializers.ModelSerializer): - levels = serializers.SerializerMethodField() + levels = serializers.SerializerMethodField(read_only=True, required=False) + level = serializers.ReadOnlyField() + started = serializers.ReadOnlyField() + finished = serializers.ReadOnlyField() class Meta: model = TournamentModel - fields = ["name", "nb_players", "nb_players_by_game", "level", "started", "finished", "levels"] + fields = ["name", "nb_players", "nb_players_by_game", "level", "started", "finished", "levels", "id"] def get_levels(self, instance): levels: [[int]] = [] diff --git a/tournament/viewset.py b/tournament/viewset.py index 0d7f585..a99caa9 100644 --- a/tournament/viewset.py +++ b/tournament/viewset.py @@ -19,21 +19,19 @@ class TournamentViewSet(viewsets.ModelViewSet): permission_classes = (permissions.IsAuthenticated,) authentication_classes = (SessionAuthentication,) - def perform_create(self, request: HttpRequest): + def perform_create(self, serializer: TournamentSerializer): - data: dict = request.data + nb_players = serializer.validated_data["nb_players"] + nb_players_by_game = serializer.validated_data["nb_players_by_game"] + level = 1 + number: int = nb_players + while (number != nb_players_by_game): + number = number // 2 + (number % 2) + level += 1 - nb_players_by_game = data.get("nb_players_by_game") - if (nb_players_by_game is None): - return Response("nb_player_by_game is required.", status=status.HTTP_400_BAD_REQUEST) - - nb_players = data.get("nb_players") - if (nb_players is None): - return Response("nb_players is required.", status=status.HTTP_400_BAD_REQUEST) - - tournament_id: int = TournamentModel().create(nb_players=nb_players, nb_players_by_game=nb_players_by_game) + tournament = serializer.save(level = level) - return Response({"tournament_id": tournament_id}, status=status.HTTP_201_CREATED) + return Response(self.serializer_class(tournament).data, status=status.HTTP_201_CREATED) def list(self, request: HttpRequest, state: str = ""): query: QuerySet @@ -42,7 +40,7 @@ class TournamentViewSet(viewsets.ModelViewSet): query = TournamentModel.objects.filter(started=True, finished=False) case "finished": query = TournamentModel.objects.filter(finished=True) - case "currently": + case "waiting": query = TournamentModel.objects.filter(started=False, finished=False) case _: query = TournamentModel.objects.all() From be43e4594792b253b46c456f8e4e2506dc0e1645 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 22:37:44 +0100 Subject: [PATCH 11/42] fix: profiles test --- profiles/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/profiles/tests.py b/profiles/tests.py index cf8d9fa..087957c 100644 --- a/profiles/tests.py +++ b/profiles/tests.py @@ -7,8 +7,8 @@ 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.expected_response = {'avatar_url': '/static/avatars/default.avif', 'user_id': 1, 'username': 'bozo'} + self.url = "/api/profiles/" def test_profile_create_on_user_created(self): From 35cd728b3c42f075ac47d803d322170bc7a35631 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 22:49:18 +0100 Subject: [PATCH 12/42] core: move tournament url --- tournament/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tournament/urls.py b/tournament/urls.py index 47abf9b..3aeecb5 100644 --- a/tournament/urls.py +++ b/tournament/urls.py @@ -6,5 +6,6 @@ from .viewset import TournamentViewSet urlpatterns = [ path("", TournamentViewSet.as_view({"get": "retrieve"}), name="tournament_page"), - re_path(r"search/(?P\w*)", TournamentViewSet.as_view({"get": "list", "post": "create"}), name="tournaments"), + path("", TournamentViewSet.as_view({"post": "create"}), name="tournament_page"), + re_path(r"search/(?P\w*)", TournamentViewSet.as_view({"get": "list", }), name="tournaments"), ] \ No newline at end of file From 789a83cb41d081d29fe6d7299e234625ee0f2bba Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 22:49:29 +0100 Subject: [PATCH 13/42] add: test to tournament --- tournament/test.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tournament/test.py diff --git a/tournament/test.py b/tournament/test.py new file mode 100644 index 0000000..3da31cb --- /dev/null +++ b/tournament/test.py @@ -0,0 +1,44 @@ +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 json +import uuid + +class CreateTest(TestCase): + def setUp(self): + self.client = Client() + + self.url = "/api/tournaments/" + + self.username = str(uuid.uuid4()) + self.password = str(uuid.uuid4()) + + self.nb_players_by_game = 2 + self.nb_players = 8 + + user: User = User.objects.create_user(username=self.username, password=self.password) + self.client.login(username=self.username, password=self.password) + + def test_normal(self): + response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": self.nb_players_by_game, "nb_players": self.nb_players}) + response_data: dict = json.loads(response.content) + self.assertDictContainsSubset({"name": ""}, response_data) + + def test_too_small_nb_players_by_game(self): + response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 1, "nb_players": self.nb_players}) + response_data = json.loads(response.content) + self.assertDictEqual(response_data, {'nb_players_by_game': ['The numbers of players by game must be greather than 2.']}) + + def test_too_small_nb_players(self): + response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": self.nb_players_by_game, "nb_players": 1}) + response_data = json.loads(response.content) + self.assertDictEqual(response_data, {'nb_players': ['The numbers of players must be greather than 2.'], 'nb_players_by_game': ['The numbers of players by game must be smaller than the numbers of players.']}) + + def test_nb_players_smaller_nb_players_by_game(self): + response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 5, "nb_players": 3}) + response_data = json.loads(response.content) + self.assertDictEqual(response_data, {'nb_players_by_game': ['The numbers of players by game must be smaller than the numbers of players.']}) \ No newline at end of file From 5ba432e9fdf4768c649df5dc4d615ce577b55ae2 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 20 Dec 2023 22:49:48 +0100 Subject: [PATCH 14/42] add: default value to tournament.name --- tournament/serializers.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tournament/serializers.py b/tournament/serializers.py index cb8054f..c17525b 100644 --- a/tournament/serializers.py +++ b/tournament/serializers.py @@ -7,6 +7,7 @@ class TournamentSerializer(serializers.ModelSerializer): level = serializers.ReadOnlyField() started = serializers.ReadOnlyField() finished = serializers.ReadOnlyField() + name = serializers.CharField(default="") class Meta: model = TournamentModel @@ -20,3 +21,18 @@ class TournamentSerializer(serializers.ModelSerializer): break levels.append(level) return levels + + def validate_nb_players(self, value: int): + if (value < 2): + raise serializers.ValidationError("The numbers of players must be greather than 2.") + return value + + def validate_nb_players_by_game(self, value: int): + if (value < 2): + raise serializers.ValidationError("The numbers of players by game must be greather than 2.") + nb_players: str = self.initial_data.get("nb_players") + if (nb_players is not None and nb_players.isnumeric()): + nb_players: int = int(nb_players) + if (value > nb_players): + raise serializers.ValidationError("The numbers of players by game must be smaller than the numbers of players.") + return value \ No newline at end of file From 0626faae7feffab4cf2c55c5ca05921728d65f28 Mon Sep 17 00:00:00 2001 From: starnakin Date: Thu, 21 Dec 2023 00:04:48 +0100 Subject: [PATCH 15/42] core: edit renderView engine to display 404 error on not found page --- frontend/static/js/index.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index 346eacb..f81cf96 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -35,6 +35,19 @@ const navigateTo = async (uri) => { history.pushState(null, null, uri); }; +async function renderView(view) +{ + let content = await view.getHtml(); + if (content == null) + return 1; + + view.setTitle(); + document.querySelector("#app").innerHTML = content + + if (await view.postInit()) + renderView(new PageNotFoundView()); +} + const router = async (uri) => { const routes = [ { path: "/", view: Dashboard }, @@ -80,14 +93,7 @@ const router = async (uri) => { lastView = view; await client.isAuthentificate(); - let content = await view.getHtml(); - if (content == null) - return 1; - - view.setTitle(); - document.querySelector("#app").innerHTML = content - - await view.postInit(); + renderView(view); return 0; }; From 8e0514514bcbb0c3afdc9f7bee3cc66bb8dc757f Mon Sep 17 00:00:00 2001 From: starnakin Date: Thu, 21 Dec 2023 00:05:54 +0100 Subject: [PATCH 16/42] fix: check if profile existe befort render profile page --- frontend/static/js/api/profile.js | 4 ++++ frontend/static/js/api/profiles.js | 3 ++- frontend/static/js/views/ProfilePageView.js | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/static/js/api/profile.js b/frontend/static/js/api/profile.js index 1ec7987..dddc1ac 100644 --- a/frontend/static/js/api/profile.js +++ b/frontend/static/js/api/profile.js @@ -19,6 +19,10 @@ class Profile async init(user_id) { let response = await this.client._get(`/api/profiles/${user_id}`); + + if (response.status === 404) + return 1; + let response_data = await response.json(); this.user_id = response_data.user_id; diff --git a/frontend/static/js/api/profiles.js b/frontend/static/js/api/profiles.js index b12691f..a83c156 100644 --- a/frontend/static/js/api/profiles.js +++ b/frontend/static/js/api/profiles.js @@ -28,7 +28,8 @@ class Profiles async getProfile(user_id) { let profile = new Profile(this.client); - await profile.init(user_id); + if (await profile.init(user_id)) + return null; return profile; } diff --git a/frontend/static/js/views/ProfilePageView.js b/frontend/static/js/views/ProfilePageView.js index 90c3d4e..0ebe4ce 100644 --- a/frontend/static/js/views/ProfilePageView.js +++ b/frontend/static/js/views/ProfilePageView.js @@ -10,6 +10,10 @@ export default class extends AbstractView { async postInit() { let profile = await client.profiles.getProfile(this.user_id); + + if (profile === null) + return 1; + let info = document.getElementById("info"); // Username @@ -18,7 +22,7 @@ export default class extends AbstractView { username.appendChild(document.createTextNode(profile.username)); info.appendChild(username); - info.appendChild(document.createElement("br")); + info.appendChild(document.createElement("br")); // Avatar let avatar = document.createElement("img"); From 6295c5120fd6ca27524fb5daeb3ebca1aa82bf6e Mon Sep 17 00:00:00 2001 From: starnakin Date: Thu, 21 Dec 2023 00:21:18 +0100 Subject: [PATCH 17/42] add: tournament page --- frontend/static/js/api/client.js | 7 +++ .../static/js/api/tournament/tournament.js | 43 +++++++++++++- .../static/js/api/tournament/tournaments.js | 57 ++++++++++++++++--- frontend/static/js/index.js | 2 + .../static/js/views/TournamentPageView.js | 55 ++++++++++++++++++ 5 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 frontend/static/js/views/TournamentPageView.js diff --git a/frontend/static/js/api/client.js b/frontend/static/js/api/client.js index ec3d54b..54fd1a4 100644 --- a/frontend/static/js/api/client.js +++ b/frontend/static/js/api/client.js @@ -3,6 +3,8 @@ import { MatchMaking } from "./matchmaking.js"; import { Profiles } from "./profiles.js"; import { Channels } from './chat/channels.js'; import { MyProfile } from "./MyProfile.js"; +import { navigateTo } from "../index.js" +import { Tourmanents } from "./tournament/tournaments.js"; function getCookie(name) { @@ -22,6 +24,7 @@ class Client this.account = new Account(this); this.profiles = new Profiles(this); this.matchmaking = new MatchMaking(this); + this.tournaments = new Tourmanents(this); this._logged = undefined; this.channels = new Channels(this); @@ -101,6 +104,10 @@ class Client this.me = new MyProfile(this); await this.me.init(); } + if (this.logged && !state) + { + navigateTo("/login"); + } this.logged = state; } diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index 7ecce9c..141ae57 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -1,18 +1,55 @@ -import { Client } from "./client.js"; +import { Client } from "../client.js"; class Tourmanent { /** * @param {Client} client */ - constructor(client) + constructor(client, name = undefined, nb_players = undefined, nb_players_by_game = undefined, level = undefined, started = undefined, finished = undefined, levels = undefined, id = undefined) { /** * @type {Client} */ - this.client = client + this.client = client; + this.name = name; + this.nb_players = nb_players; + this.nb_players_by_game = nb_players_by_game; + this.level = level; + this.started = started; + this.finished = finished; + this.levels = levels; + this.state = this.get_state(); + this.id = id } + get_state() + { + if (this.finished) + return "finished"; + if (this.started) + return "started"; + else + return "waiting"; + } + + async init(id) + { + let response = await this.client._get(`/api/tournaments/${id}`); + + if (response.status === 404) + return null; + + let response_data = await response.json(); + + this.name = response_data.name; + this.nb_players = response_data.nb_players; + this.nb_players_by_game = response_data.nb_players_by_game; + this.level = response_data.level; + this.started = response_data.started; + this.finished = response_data.finished; + this.levels = response_data.levels; + this.id = response_data.id + } } diff --git a/frontend/static/js/api/tournament/tournaments.js b/frontend/static/js/api/tournament/tournaments.js index 60a92a7..8424500 100644 --- a/frontend/static/js/api/tournament/tournaments.js +++ b/frontend/static/js/api/tournament/tournaments.js @@ -1,4 +1,5 @@ -import { Client } from "./client.js"; +import { Client } from "../client.js"; +import { Tourmanent } from "./tournament.js"; class Tourmanents { @@ -13,19 +14,59 @@ class Tourmanents this.client = client } - async createTournament(nb_players, online) + async getTournament(id) { - if (online) - return "offline"; - let response = await this.client._post("/api/tournaments/", {numbers_of_players: nb_players}); - let response_data = await response.json(); + let tournament = new Tourmanent(this.client); + await tournament.init(id); + return tournament; + } - if (JSON.stringify(response_data) === JSON.stringify({'detail': 'Authentication credentials were not provided.'})) + /** + * @param {boolean} online if the tournament is online or offline + */ + async createTournament(nb_players, nb_players_by_game, name = "", online = true) + { + if (online === false) + { + let tournament = new Tourmanent(this.client, nb_players, nb_players_by_game, 5, false, false, [], 0); + return tournament; + } + let response = await this.client._post("/api/tournaments/", {nb_players: nb_players, nb_players_by_game: nb_players_by_game, name: name}); + + if (response.status === 403) { this.client._update_logged(false); return null; } - return response_data.tournament_id; + + let response_data = await response.json(); + + let tournament = this.getTournament(response_data["id"]); + + return tournament; + } + + /** + * @param {string} state must be "finished", or "started", or "waiting". Any other return all elements + */ + + async search(state) + { + let response = await this.client._get(`/api/tournaments/search/${state}`); + let response_data = await response.json() + + if (response.status === 404) + { + this.client._update_logged(false); + return null; + } + + return response_data; + } + + async all() + { + return await this.search(""); } } diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index f81cf96..f4d8510 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -14,6 +14,7 @@ import AbstractRedirectView from "./views/abstracts/AbstractRedirectView.js"; import MeView from "./views/MeView.js"; import ProfilePageView from "./views/ProfilePageView.js"; import MatchMakingView from "./views/MatchMakingView.js"; +import TournamentPageView from "./views/TournamentPageView.js"; let client = new Client(location.protocol + "//" + location.host) @@ -52,6 +53,7 @@ const router = async (uri) => { const routes = [ { path: "/", view: Dashboard }, { path: "/profiles/:id", view: ProfilePageView }, + { path: "/tournaments/:id", view: TournamentPageView }, { path: "/login", view: LoginView }, { path: "/logout", view: LogoutView }, { path: "/register", view: RegisterView }, diff --git a/frontend/static/js/views/TournamentPageView.js b/frontend/static/js/views/TournamentPageView.js new file mode 100644 index 0000000..92c4afd --- /dev/null +++ b/frontend/static/js/views/TournamentPageView.js @@ -0,0 +1,55 @@ +import {client} from "../index.js"; +import AbstractAuthentifiedView from "./abstracts/AbstractAuthentifiedView.js"; + +export default class extends AbstractAuthentifiedView { + constructor(params) { + super(params, "Tournament"); + this.id = params.id; + } + + async postInit() + { + let tournament = await client.tournaments.getTournament(this.id); + + if (tournament === null) + return 1; + + document.getElementById("name").innerText = tournament.name || `${tournament.nb_players_by_game}x1, ${tournament.nb_players} players`; + document.getElementById("nb_players").innerText = tournament.nb_players; + document.getElementById("nb_players_by_game").innerText = tournament.nb_players_by_game; + document.getElementById("level").innerText = tournament.level; + document.getElementById("state").innerText = tournament.state; + + } + + async getHtml() + { + return ` + + + + + + + + + + + + + + + + + + + + + + + + +
Loading...
Number of playersLoading...
Number of players by gameLoading...
Number of roundLoading...
statusLoading...
+ ` + } +} From 4eaa35584d891b369f3627df0de7b07f7409edca Mon Sep 17 00:00:00 2001 From: starnakin Date: Thu, 21 Dec 2023 00:23:10 +0100 Subject: [PATCH 18/42] fix: return 404 when tournament not found --- frontend/static/js/api/tournament/tournament.js | 2 +- frontend/static/js/api/tournament/tournaments.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index 141ae57..c33da46 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -37,7 +37,7 @@ class Tourmanent let response = await this.client._get(`/api/tournaments/${id}`); if (response.status === 404) - return null; + return 1; let response_data = await response.json(); diff --git a/frontend/static/js/api/tournament/tournaments.js b/frontend/static/js/api/tournament/tournaments.js index 8424500..3383cd5 100644 --- a/frontend/static/js/api/tournament/tournaments.js +++ b/frontend/static/js/api/tournament/tournaments.js @@ -17,7 +17,8 @@ class Tourmanents async getTournament(id) { let tournament = new Tourmanent(this.client); - await tournament.init(id); + if (await tournament.init(id)) + return null; return tournament; } From bcf686815025894cc4fcb0b2a5892ce82f110027 Mon Sep 17 00:00:00 2001 From: starnakin Date: Thu, 21 Dec 2023 10:41:39 +0100 Subject: [PATCH 19/42] fix: de golmon --- matchmaking/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/matchmaking/models.py b/matchmaking/models.py index 71a8362..78dda1b 100644 --- a/matchmaking/models.py +++ b/matchmaking/models.py @@ -1,3 +1,11 @@ from django.db import models # Create your models here. +in_matchmaking = { + "tournaments": { + + }, + "normal": { + + } +} \ No newline at end of file From 587980d637ffc51c7f1f3dcc237c2c71feda9aae Mon Sep 17 00:00:00 2001 From: starnakin Date: Thu, 21 Dec 2023 11:35:47 +0100 Subject: [PATCH 20/42] add: tournament list view --- .../static/js/api/tournament/tournament.js | 4 +- .../static/js/api/tournament/tournaments.js | 16 ++- frontend/static/js/index.js | 2 + .../static/js/views/TournamentPageView.js | 8 +- .../static/js/views/TournamentsListView.js | 133 ++++++++++++++++++ 5 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 frontend/static/js/views/TournamentsListView.js diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index c33da46..5a4ca37 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -11,7 +11,7 @@ class Tourmanent * @type {Client} */ this.client = client; - this.name = name; + this.name = name || `${nb_players_by_game}x1, ${nb_players} players`; this.nb_players = nb_players; this.nb_players_by_game = nb_players_by_game; this.level = level; @@ -41,7 +41,7 @@ class Tourmanent let response_data = await response.json(); - this.name = response_data.name; + this.name = response_data.name || `${response_data.nb_players_by_game}x1, ${response_data.nb_players} players`; this.nb_players = response_data.nb_players; this.nb_players_by_game = response_data.nb_players_by_game; this.level = response_data.level; diff --git a/frontend/static/js/api/tournament/tournaments.js b/frontend/static/js/api/tournament/tournaments.js index 3383cd5..149f73d 100644 --- a/frontend/static/js/api/tournament/tournaments.js +++ b/frontend/static/js/api/tournament/tournaments.js @@ -62,7 +62,21 @@ class Tourmanents return null; } - return response_data; + let tournaments = []; + + response_data.forEach(tournament_data => { + tournaments.push(new Tourmanent(this.client, + tournament_data.name, + tournament_data.nb_players, + tournament_data.nb_players_by_game, + tournament_data.level, + tournament_data.started, + tournament_data.finished, + tournament_data.levels, + tournament_data.id)); + }); + + return tournaments; } async all() diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index f4d8510..1bd8f6e 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -15,6 +15,7 @@ import MeView from "./views/MeView.js"; import ProfilePageView from "./views/ProfilePageView.js"; import MatchMakingView from "./views/MatchMakingView.js"; import TournamentPageView from "./views/TournamentPageView.js"; +import TournamentsView from "./views/TournamentsListView.js"; let client = new Client(location.protocol + "//" + location.host) @@ -54,6 +55,7 @@ const router = async (uri) => { { path: "/", view: Dashboard }, { path: "/profiles/:id", view: ProfilePageView }, { path: "/tournaments/:id", view: TournamentPageView }, + { path: "/tournaments/", view: TournamentsView}, { path: "/login", view: LoginView }, { path: "/logout", view: LogoutView }, { path: "/register", view: RegisterView }, diff --git a/frontend/static/js/views/TournamentPageView.js b/frontend/static/js/views/TournamentPageView.js index 92c4afd..a7787af 100644 --- a/frontend/static/js/views/TournamentPageView.js +++ b/frontend/static/js/views/TournamentPageView.js @@ -1,8 +1,10 @@ import {client} from "../index.js"; import AbstractAuthentifiedView from "./abstracts/AbstractAuthentifiedView.js"; -export default class extends AbstractAuthentifiedView { - constructor(params) { +export default class extends AbstractAuthentifiedView +{ + constructor(params) + { super(params, "Tournament"); this.id = params.id; } @@ -14,7 +16,7 @@ export default class extends AbstractAuthentifiedView { if (tournament === null) return 1; - document.getElementById("name").innerText = tournament.name || `${tournament.nb_players_by_game}x1, ${tournament.nb_players} players`; + document.getElementById("name").innerText = tournament.name; document.getElementById("nb_players").innerText = tournament.nb_players; document.getElementById("nb_players_by_game").innerText = tournament.nb_players_by_game; document.getElementById("level").innerText = tournament.level; diff --git a/frontend/static/js/views/TournamentsListView.js b/frontend/static/js/views/TournamentsListView.js new file mode 100644 index 0000000..12794ca --- /dev/null +++ b/frontend/static/js/views/TournamentsListView.js @@ -0,0 +1,133 @@ +import {client} from "../index.js"; +import AbstractAuthentifiedView from "./abstracts/AbstractAuthentifiedView.js"; + +export default class extends AbstractAuthentifiedView +{ + constructor(params) + { + super(params, "Tournament"); + this.id = params.id; + } + + async external_search() + { + let state = document.getElementById("state-select").value; + this.tournaments = await client.tournaments.search(state); + } + + add_nb_player_by_game_selector() + { + let nb_players_by_game_list = new Set() + this.tournaments.forEach(tournament => { + nb_players_by_game_list.add(tournament.nb_players_by_game); + }); + + let select = document.getElementById("nb-players-by-game-select"); + + let new_children = [] + + const opt = document.createElement("option"); + opt.value = "all"; + opt.text = "All"; + + new_children.push(opt); + + nb_players_by_game_list.forEach(nb_players_by_game => { + const opt = document.createElement("option"); + opt.value = nb_players_by_game; + opt.text = nb_players_by_game; + new_children.push(opt); + }) + select.replaceChildren(...new_children); + } + + internal_search() + { + let nb_players_by_game = document.getElementById("nb-players-by-game-select").value; + + this.display_tournaments = []; + this.tournaments.forEach(tournament => { + if (nb_players_by_game === "all" || nb_players_by_game == tournament.nb_players_by_game) + this.display_tournaments.push(tournament); + }); + } + + display_result() + { + const tournaments_list = document.getElementById("tournaments-list"); + + const new_children = [] + + this.display_tournaments.forEach(tournament => { + + let tr = document.createElement("tr"); + + // name + let td = document.createElement("td"); + td.innerText = tournament.name; + tr.appendChild(td); + + // state + td = document.createElement("td"); + td.innerText = tournament.state; + tr.appendChild(td); + + // nb_players + td = document.createElement("td"); + td.innerText = tournament.nb_players; + tr.appendChild(td); + + // nb_players_by_game + td = document.createElement("td"); + td.innerText = tournament.nb_players_by_game; + tr.appendChild(td); + + new_children.push(tr); + }); + tournaments_list.replaceChildren(...new_children); + } + + async update_query() + { + this.internal_search(); + this.display_result(); + } + + async update_search() + { + await this.external_search(); + this.add_nb_player_by_game_selector(); + this.update_query(); + } + + async postInit() + { + await this.update_search() + document.getElementById("state-select").onchange = this.update_search.bind(this); + document.getElementById("nb-players-by-game-select").onchange = this.update_query.bind(this); + } + + async getHtml() + { + return ` + + + + + + + + + + + +
NameStatusMax numbers of playersMax numbers of players by game
+ ` + } +} From 278e2cbe548325efac55455ff81db1ff4a1daa5a Mon Sep 17 00:00:00 2001 From: starnakin Date: Fri, 22 Dec 2023 14:26:06 +0100 Subject: [PATCH 21/42] add: tournament create view --- .../static/js/api/tournament/tournaments.js | 15 +----- frontend/static/js/index.js | 4 +- .../static/js/views/TournamentCreateView.js | 52 +++++++++++++++++++ 3 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 frontend/static/js/views/TournamentCreateView.js diff --git a/frontend/static/js/api/tournament/tournaments.js b/frontend/static/js/api/tournament/tournaments.js index 149f73d..8fb0489 100644 --- a/frontend/static/js/api/tournament/tournaments.js +++ b/frontend/static/js/api/tournament/tournaments.js @@ -22,16 +22,8 @@ class Tourmanents return tournament; } - /** - * @param {boolean} online if the tournament is online or offline - */ - async createTournament(nb_players, nb_players_by_game, name = "", online = true) + async createTournament(nb_players, nb_players_by_game, name = "") { - if (online === false) - { - let tournament = new Tourmanent(this.client, nb_players, nb_players_by_game, 5, false, false, [], 0); - return tournament; - } let response = await this.client._post("/api/tournaments/", {nb_players: nb_players, nb_players_by_game: nb_players_by_game, name: name}); if (response.status === 403) @@ -41,10 +33,7 @@ class Tourmanents } let response_data = await response.json(); - - let tournament = this.getTournament(response_data["id"]); - - return tournament; + return response_data; } /** diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index 1bd8f6e..db5c608 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -16,6 +16,7 @@ import ProfilePageView from "./views/ProfilePageView.js"; import MatchMakingView from "./views/MatchMakingView.js"; import TournamentPageView from "./views/TournamentPageView.js"; import TournamentsView from "./views/TournamentsListView.js"; +import TournamentCreateView from "./views/TournamentCreateView.js"; let client = new Client(location.protocol + "//" + location.host) @@ -54,8 +55,9 @@ const router = async (uri) => { const routes = [ { path: "/", view: Dashboard }, { path: "/profiles/:id", view: ProfilePageView }, + { path: "/tournaments/create", view: TournamentCreateView }, { path: "/tournaments/:id", view: TournamentPageView }, - { path: "/tournaments/", view: TournamentsView}, + { path: "/tournaments/", view: TournamentsView }, { path: "/login", view: LoginView }, { path: "/logout", view: LogoutView }, { path: "/register", view: RegisterView }, diff --git a/frontend/static/js/views/TournamentCreateView.js b/frontend/static/js/views/TournamentCreateView.js new file mode 100644 index 0000000..ef93428 --- /dev/null +++ b/frontend/static/js/views/TournamentCreateView.js @@ -0,0 +1,52 @@ +import {client, navigateTo} from "../index.js"; +import { clear, fill_errors } from "../utils/formUtils.js"; +import AbstractAuthentifiedView from "./abstracts/AbstractAuthentifiedView.js"; + +export default class extends AbstractAuthentifiedView +{ + constructor(params) + { + super(params, "Create tournament"); + this.id = params.id; + } + + async create() + { + let name = document.getElementById("name-input").value; + let nb_players = document.getElementById("nb_players-input").value; + let nb_players_by_game = document.getElementById("nb_players_by_game-input").value + + let response_data = await client.tournaments.createTournament(nb_players, nb_players_by_game, name); + + if (response_data === null) + return; + + let id = response_data["id"] + if (id !== undefined) + { + navigateTo(`/tournaments/${id}`); + return; + } + + clear("innerHTML", ["name", "nb_players", "nb_players_by_game"]); + fill_errors(response_data, "innerHTML"); + } + + async postInit() + { + document.getElementById("create-button").onclick = this.create; + } + + async getHtml() + { + return ` + + + + + + + + ` + } +} From 6c39a13aca321bb8e1ac5ca8711260f09f88f5ad Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 12:54:33 +0100 Subject: [PATCH 22/42] core: recreation of matchmaking, add: matchmaking support multiple modes --- frontend/static/js/api/chat/channel.js | 2 +- frontend/static/js/api/matchmaking.js | 14 +-- frontend/static/js/views/MatchMakingView.js | 32 ++++++- games/models.py | 2 +- matchmaking/consumers.py | 34 +++----- matchmaking/models.py | 97 +++++++++++++++++++-- matchmaking/routing.py | 2 +- tournament/viewset.py | 1 - 8 files changed, 143 insertions(+), 41 deletions(-) diff --git a/frontend/static/js/api/chat/channel.js b/frontend/static/js/api/chat/channel.js index 13c3db6..5b36dd9 100644 --- a/frontend/static/js/api/chat/channel.js +++ b/frontend/static/js/api/chat/channel.js @@ -14,7 +14,7 @@ class Channel { // reload = function to use when we receive a message async connect(reload) { - let url = `ws://${window.location.host}/ws/chat/${this.channel_id}/`; + let url = `wss://${window.location.host}/ws/chat/${this.channel_id}/`; this.chatSocket = new WebSocket(url); this.chatSocket.onmessage = (event) =>{ diff --git a/frontend/static/js/api/matchmaking.js b/frontend/static/js/api/matchmaking.js index ce0adc8..8dd76e5 100644 --- a/frontend/static/js/api/matchmaking.js +++ b/frontend/static/js/api/matchmaking.js @@ -11,18 +11,21 @@ class MatchMaking * @type {Client} */ this.client = client + this.searching = false; } - async start(func) + async start(func, mode) { if (!await this.client.isAuthentificate()) return null; - let url = `wss://${window.location.host}/ws/matchmaking/`; + let url = `wss://${window.location.host}/ws/matchmaking/${mode}`; - this._chatSocket = new WebSocket(url); + this._socket = new WebSocket(url); - this._chatSocket.onmessage = function (event) { + this.searching = true; + + this._socket.onmessage = function (event) { const data = JSON.parse(event.data); func(data.game_id) }; @@ -30,7 +33,8 @@ class MatchMaking async stop() { - this._chatSocket.close() + this.searching = false; + this._socket.close() } } diff --git a/frontend/static/js/views/MatchMakingView.js b/frontend/static/js/views/MatchMakingView.js index ca02363..01c0522 100644 --- a/frontend/static/js/views/MatchMakingView.js +++ b/frontend/static/js/views/MatchMakingView.js @@ -7,18 +7,42 @@ function game_found(game_id) } export default class extends AbstractView { - constructor(params) { - super(params, "Dashboard"); + constructor(params) + { + super(params, "Matchmaking"); + } + + async press_button() + { + if (client.matchmaking.searching) + { + client.matchmaking.stop(); + document.getElementById("button").value = "Find a game" + } + else + { + await this.matchmaking(); + document.getElementById("button").value = "Stop matchmaking" + } + } + + async matchmaking() + { + let nb_players = document.getElementById("nb_players-input").value + + client.matchmaking.start(game_found, nb_players); } async postInit() { - await client.matchmaking.start(game_found) + document.getElementById("button").onclick = this.matchmaking } async getHtml() { return ` -

finding

+

Select mode

+ + `; } diff --git a/games/models.py b/games/models.py index 0493dc5..514e52c 100644 --- a/games/models.py +++ b/games/models.py @@ -5,7 +5,7 @@ class GameModel(models.Model): finished = models.BooleanField(default=False) started = models.BooleanField(default=False) - winner_id = models.IntegerField(null=True, blank=True) + winner_id = models.IntegerField(default=-1) def create(self, users_id: [int]): self.save() diff --git a/matchmaking/consumers.py b/matchmaking/consumers.py index dfa65e8..56b0280 100644 --- a/matchmaking/consumers.py +++ b/matchmaking/consumers.py @@ -6,8 +6,7 @@ from games.models import GameModel import json -queue_id: [int] = [] -queue_ws: [WebsocketConsumer] = [] +from .models import Waiter, WaitingRoom, WaitingRoomManager, normal class MatchMaking(WebsocketConsumer): @@ -24,25 +23,20 @@ class MatchMaking(WebsocketConsumer): self.channel_layer.group_add(self.group_name, self.channel_name) - self.accept() + self.mode = int(self.scope['url_route']['kwargs']['mode']) + self.group_name = self.mode - global queue_id, queue_ws - queue_id.append(user.pk) - queue_ws.append(self) + waiting_room: WaitingRoom = normal.get(self.mode) + waiting_room.append(Waiter(user.pk, 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() + print(len(waiting_room), "/", self.mode, [len(waiting_room),self.mode]) + if (len(waiting_room) == self.mode): + game_id: int = GameModel().create(waiting_room.get_users_id()) + waiting_room.broadcast({"detail": "Game found !", "game_id": game_id}) + waiting_room.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) \ No newline at end of file + waiting_room: WaitingRoom = normal.get(self.mode) + waiter: Waiter = waiting_room.get_waiter_by_socket(self) + if (waiter is not None): + waiting_room.remove(self.scope["user"].pk) \ No newline at end of file diff --git a/matchmaking/models.py b/matchmaking/models.py index 78dda1b..421bef8 100644 --- a/matchmaking/models.py +++ b/matchmaking/models.py @@ -1,11 +1,92 @@ from django.db import models -# Create your models here. -in_matchmaking = { - "tournaments": { +from channels.generic.websocket import WebsocketConsumer +import json - }, - "normal": { - - } -} \ No newline at end of file +# Create your models here. +class Waiter: + + def __init__(self, user_id: int, socket: WebsocketConsumer): + self.user_id: int = user_id + self.socket: WebsocketConsumer = socket + + def send(self, data: dict): + self.socket.send(text_data=json.dumps(data)) + + def accept(self): + self.socket.accept() + + def disconnect(self): + self.socket.disconnect() + + def __eq__(self, obj): + return self.user_id == obj.user_id + +class WaitingRoom: + + def __init__(self, waiting_room_manager, mode: int): + self._waiter_list: [Waiter] = [] + self._mode: int = mode + self._waiting_room_manager = waiting_room_manager + + def broadcast(self, data: dict): + for waiter in self._waiter_list: + waiter: Waiter + waiter.send(data) + + def clear(self): + self._waiter_list.clear() + + def get_waiter_by_socket(self, socket: WebsocketConsumer): + for waiter in self._waiter_list: + waiter: Waiter + if (waiter.socket is socket): + return waiter + return None + + def append(self, waiter: Waiter): + + self.remove(waiter) + waiter.accept() + self._waiter_list.append(waiter) + + def remove(self, users_id): + for waiter in self._waiter_list: + waiter: Waiter = waiter + if (waiter == users_id): + waiter.disconnect() + if (self.empty()): + self._waiting_room_manager.remove(self) + return + + def empty(self): + for _ in self._waiter_list: + return False + return True + + def get_users_id(self): + return [waiter.user_id for waiter in self._waiter_list] + + def __len__(self): + return len(self._waiter_list) + + + +class WaitingRoomManager: + + def __init__(self): + self._waiting_rooms: [WaitingRoom] = [] + + def get(self, mode: int): + for waiting_room in self._waiting_rooms: + waiting_room: WaitingRoom = waiting_room + if (waiting_room._mode == mode): + return waiting_room + tmp: WaitingRoom = WaitingRoom(self, mode) + self._waiting_rooms.append(tmp) + return tmp + + def remove(self, waiting_room: WaitingRoom): + self._waiting_rooms.remove(waiting_room) + +normal: WaitingRoomManager = WaitingRoomManager() \ No newline at end of file diff --git a/matchmaking/routing.py b/matchmaking/routing.py index eed5072..417779b 100644 --- a/matchmaking/routing.py +++ b/matchmaking/routing.py @@ -2,5 +2,5 @@ from django.urls import re_path from . import consumers websocket_urlpatterns = [ - re_path(r'ws/matchmaking/', consumers.MatchMaking.as_asgi()) + re_path(r'ws/matchmaking/(?P\d+)$', consumers.MatchMaking.as_asgi()) ] diff --git a/tournament/viewset.py b/tournament/viewset.py index a99caa9..bc27f11 100644 --- a/tournament/viewset.py +++ b/tournament/viewset.py @@ -7,7 +7,6 @@ from django.http import HttpRequest from django.contrib.auth import login from django.db.models import QuerySet -from matchmaking.models import in_matchmaking from .models import TournamentModel from .serializers import TournamentSerializer From c02c49215deb7eb6c2b7add1bd77d3bc2d70122a Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 16:47:22 +0100 Subject: [PATCH 23/42] fix: matchmaking support start stop, multi connection --- frontend/static/js/api/matchmaking.js | 15 +++++++- frontend/static/js/views/MatchMakingView.js | 26 +++++++------ matchmaking/consumers.py | 2 +- matchmaking/models.py | 40 +++++++++++--------- tournament/consumers.py | 41 +++++++++++++++++++++ 5 files changed, 92 insertions(+), 32 deletions(-) create mode 100644 tournament/consumers.py diff --git a/frontend/static/js/api/matchmaking.js b/frontend/static/js/api/matchmaking.js index 8dd76e5..e33ecf9 100644 --- a/frontend/static/js/api/matchmaking.js +++ b/frontend/static/js/api/matchmaking.js @@ -14,7 +14,7 @@ class MatchMaking this.searching = false; } - async start(func, mode) + async start(receive_func, disconnect_func, mode) { if (!await this.client.isAuthentificate()) return null; @@ -25,10 +25,21 @@ class MatchMaking this.searching = true; + this.receive_func = receive_func; + this.disconnect_func = disconnect_func; + this._socket.onmessage = function (event) { const data = JSON.parse(event.data); - func(data.game_id) + receive_func(data); }; + + this._socket.onclose = this.onclose.bind(this); + } + + onclose(event) + { + this.stop(); + this.disconnect_func() } async stop() diff --git a/frontend/static/js/views/MatchMakingView.js b/frontend/static/js/views/MatchMakingView.js index 01c0522..c9c33ca 100644 --- a/frontend/static/js/views/MatchMakingView.js +++ b/frontend/static/js/views/MatchMakingView.js @@ -1,11 +1,7 @@ import { client, navigateTo } from "../index.js"; +import { clear, fill_errors } from "../utils/formUtils.js"; import AbstractView from "./abstracts/AbstractView.js"; -function game_found(game_id) -{ - navigateTo(`/games/${game_id}`) -} - export default class extends AbstractView { constructor(params) { @@ -21,28 +17,36 @@ export default class extends AbstractView { } else { - await this.matchmaking(); + let nb_players = document.getElementById("nb_players-input").value + + await client.matchmaking.start(this.display_data, this.ondisconnect, nb_players); + document.getElementById("button").value = "Stop matchmaking" } } - async matchmaking() + ondisconnect() { - let nb_players = document.getElementById("nb_players-input").value + document.getElementById("button").value = "Find a game" + } - client.matchmaking.start(game_found, nb_players); + display_data(data) + { + clear("innerText", ["detail"]); + fill_errors(data, "innerText"); } async postInit() { - document.getElementById("button").onclick = this.matchmaking + document.getElementById("button").onclick = this.press_button.bind(this) } async getHtml() { return ` -

Select mode

+

Select mode

+ `; } diff --git a/matchmaking/consumers.py b/matchmaking/consumers.py index 56b0280..1b6fbbf 100644 --- a/matchmaking/consumers.py +++ b/matchmaking/consumers.py @@ -39,4 +39,4 @@ class MatchMaking(WebsocketConsumer): waiting_room: WaitingRoom = normal.get(self.mode) waiter: Waiter = waiting_room.get_waiter_by_socket(self) if (waiter is not None): - waiting_room.remove(self.scope["user"].pk) \ No newline at end of file + waiting_room.remove(waiter) \ No newline at end of file diff --git a/matchmaking/models.py b/matchmaking/models.py index 421bef8..a1f3fc5 100644 --- a/matchmaking/models.py +++ b/matchmaking/models.py @@ -10,17 +10,16 @@ class Waiter: self.user_id: int = user_id self.socket: WebsocketConsumer = socket - def send(self, data: dict): - self.socket.send(text_data=json.dumps(data)) + def send(self, detail: str, data: dict = {}): + raw_data: dict = {"detail": detail} + raw_data.update(data) + self.socket.send(text_data=json.dumps(raw_data)) def accept(self): self.socket.accept() def disconnect(self): - self.socket.disconnect() - - def __eq__(self, obj): - return self.user_id == obj.user_id + self.socket.disconnect(200) class WaitingRoom: @@ -29,10 +28,10 @@ class WaitingRoom: self._mode: int = mode self._waiting_room_manager = waiting_room_manager - def broadcast(self, data: dict): + def broadcast(self, detail: str, data: dict = {}): for waiter in self._waiter_list: waiter: Waiter - waiter.send(data) + waiter.send(detail, data) def clear(self): self._waiter_list.clear() @@ -44,20 +43,25 @@ class WaitingRoom: return waiter return None - def append(self, waiter: Waiter): + def get_waiter_by_user_id(self, user_id: int): + for waiter in self._waiter_list: + waiter: Waiter + if (waiter.user_id == user_id): + return waiter + return None - self.remove(waiter) + + def append(self, waiter: Waiter): + tmp: Waiter = self.get_waiter_by_user_id(waiter.user_id) + if (tmp is not None): + tmp.send("Connection close: Another connection open with the same user id.") + self.remove(tmp) waiter.accept() self._waiter_list.append(waiter) - def remove(self, users_id): - for waiter in self._waiter_list: - waiter: Waiter = waiter - if (waiter == users_id): - waiter.disconnect() - if (self.empty()): - self._waiting_room_manager.remove(self) - return + def remove(self, waiter: Waiter): + self._waiter_list.remove(waiter) + waiter.disconnect() def empty(self): for _ in self._waiter_list: diff --git a/tournament/consumers.py b/tournament/consumers.py new file mode 100644 index 0000000..d021226 --- /dev/null +++ b/tournament/consumers.py @@ -0,0 +1,41 @@ +from channels.generic.websocket import WebsocketConsumer + +from django.contrib.auth.models import User + +from games.models import GameModel + +import json + +from .models import Waiter, WaitingRoom, WaitingRoomManager, normal + +class TournamentWaitingRoom(WebsocketConsumer): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.channel_name = "tournament" + self.group_name = "tournament" + + 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.tournament_id = int(self.scope['url_route']['kwargs']['tournament_id']) + + waiting_room: WaitingRoom = normal.get(self.mode) + waiting_room.append(Waiter(user.pk, self)) + + print(len(waiting_room), "/", self.mode, [len(waiting_room),self.mode]) + if (len(waiting_room) == self.mode): + game_id: int = GameModel().create(waiting_room.get_users_id()) + waiting_room.broadcast({"detail": "Game found !", "game_id": game_id}) + waiting_room.clear() + + def disconnect(self, close_code): + waiting_room: WaitingRoom = normal.get(self.mode) + waiter: Waiter = waiting_room.get_waiter_by_socket(self) + if (waiter is not None): + waiting_room.remove(self.scope["user"].pk) \ No newline at end of file From 8529963f973962c53fff27f2e9e050bc8b21751a Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 16:49:43 +0100 Subject: [PATCH 24/42] change matchmaking to authenfied view --- frontend/static/js/views/MatchMakingView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/static/js/views/MatchMakingView.js b/frontend/static/js/views/MatchMakingView.js index c9c33ca..d9f4fb5 100644 --- a/frontend/static/js/views/MatchMakingView.js +++ b/frontend/static/js/views/MatchMakingView.js @@ -1,8 +1,8 @@ import { client, navigateTo } from "../index.js"; import { clear, fill_errors } from "../utils/formUtils.js"; -import AbstractView from "./abstracts/AbstractView.js"; +import AbstractAuthentifiedView from "./abstracts/AbstractAuthentifiedView.js"; -export default class extends AbstractView { +export default class extends AbstractAuthentifiedView { constructor(params) { super(params, "Matchmaking"); From 39ea33cc8ed711a642bef8cd139a07f1313d62c6 Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 16:56:04 +0100 Subject: [PATCH 25/42] fix: go to url when game found --- frontend/static/js/views/MatchMakingView.js | 12 +++++++++++- matchmaking/consumers.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/static/js/views/MatchMakingView.js b/frontend/static/js/views/MatchMakingView.js index d9f4fb5..bc47559 100644 --- a/frontend/static/js/views/MatchMakingView.js +++ b/frontend/static/js/views/MatchMakingView.js @@ -19,7 +19,7 @@ export default class extends AbstractAuthentifiedView { { let nb_players = document.getElementById("nb_players-input").value - await client.matchmaking.start(this.display_data, this.ondisconnect, nb_players); + await client.matchmaking.start(this.onreceive.bind(this), this.ondisconnect.bind(this), nb_players); document.getElementById("button").value = "Stop matchmaking" } @@ -30,6 +30,16 @@ export default class extends AbstractAuthentifiedView { document.getElementById("button").value = "Find a game" } + onreceive(data) + { + if (data.detail === "game_found") + { + navigateTo(`/games/${data.game_id}`); + return; + } + this.display_data(data) + } + display_data(data) { clear("innerText", ["detail"]); diff --git a/matchmaking/consumers.py b/matchmaking/consumers.py index 1b6fbbf..f47d73d 100644 --- a/matchmaking/consumers.py +++ b/matchmaking/consumers.py @@ -32,7 +32,7 @@ class MatchMaking(WebsocketConsumer): print(len(waiting_room), "/", self.mode, [len(waiting_room),self.mode]) if (len(waiting_room) == self.mode): game_id: int = GameModel().create(waiting_room.get_users_id()) - waiting_room.broadcast({"detail": "Game found !", "game_id": game_id}) + waiting_room.broadcast("game_found", {"game_id": game_id}) waiting_room.clear() def disconnect(self, close_code): From f9bf3a566e46511d28f03780abf5ef0588da1e91 Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 16:58:03 +0100 Subject: [PATCH 26/42] matchmaking: add: players counter in room --- matchmaking/consumers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/matchmaking/consumers.py b/matchmaking/consumers.py index f47d73d..791d6ef 100644 --- a/matchmaking/consumers.py +++ b/matchmaking/consumers.py @@ -28,8 +28,7 @@ class MatchMaking(WebsocketConsumer): waiting_room: WaitingRoom = normal.get(self.mode) waiting_room.append(Waiter(user.pk, self)) - - print(len(waiting_room), "/", self.mode, [len(waiting_room),self.mode]) + waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room._mode}") if (len(waiting_room) == self.mode): game_id: int = GameModel().create(waiting_room.get_users_id()) waiting_room.broadcast("game_found", {"game_id": game_id}) From 0db73216b3a21543f30a326dbbf6c6a5363ae99c Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 18:11:30 +0100 Subject: [PATCH 27/42] rename the project --- manage.py | 2 +- {trancendence => transcendence}/__init__.py | 0 transcendence/abstract/AbstractRoom.py | 53 +++++++++++++++++++ transcendence/abstract/AbstractRoomManager.py | 18 +++++++ transcendence/abstract/AbstractRoomMember.py | 16 ++++++ {trancendence => transcendence}/asgi.py | 0 {trancendence => transcendence}/settings.py | 8 +-- {trancendence => transcendence}/urls.py | 0 {trancendence => transcendence}/wsgi.py | 0 9 files changed, 92 insertions(+), 5 deletions(-) rename {trancendence => transcendence}/__init__.py (100%) create mode 100644 transcendence/abstract/AbstractRoom.py create mode 100644 transcendence/abstract/AbstractRoomManager.py create mode 100644 transcendence/abstract/AbstractRoomMember.py rename {trancendence => transcendence}/asgi.py (100%) rename {trancendence => transcendence}/settings.py (95%) rename {trancendence => transcendence}/urls.py (100%) rename {trancendence => transcendence}/wsgi.py (100%) diff --git a/manage.py b/manage.py index dd64115..9294fec 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'transcendence.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/trancendence/__init__.py b/transcendence/__init__.py similarity index 100% rename from trancendence/__init__.py rename to transcendence/__init__.py diff --git a/transcendence/abstract/AbstractRoom.py b/transcendence/abstract/AbstractRoom.py new file mode 100644 index 0000000..2a2b439 --- /dev/null +++ b/transcendence/abstract/AbstractRoom.py @@ -0,0 +1,53 @@ +from .AbstractRoomMember import AbstractRoomMember + +class AbstractRoom: + + def __init__(self, room_manager): + self._member_list: [AbstractRoomMember] = [] + self.room_manager = room_manager + + def broadcast(self, detail: str, data: dict = {}): + for member in self._member_list: + member: AbstractRoomMember + member.send(detail, data) + + def clear(self): + self._member_list.clear() + + def get_member_by_socket(self, socket: WebsocketConsumer): + for member in self._member_list: + member: AbstractRoomMember + if (member.socket is socket): + return member + return None + + def get_member_by_user_id(self, user_id: int): + for member in self._member_list: + member: AbstractRoomMember + if (member.user_id == user_id): + return member + return None + + + def append(self, member: AbstractRoomMember): + tmp: AbstractRoomMember = self.get_member_by_user_id(member.user_id) + if (tmp is not None): + tmp.send("Connection close: Another connection open with the same user id.") + self.remove(tmp) + member.accept() + self._member_list.append(waiter) + + def remove(self, member: AbstractRoomMember): + self._member_list.remove(member) + waiter.disconnect() + + def empty(self): + for _ in self._waiter_list: + return False + return True + + def get_users_id(self): + return [waiter.user_id for waiter in self._waiter_list] + + def __len__(self): + return len(self._waiter_list) \ No newline at end of file diff --git a/transcendence/abstract/AbstractRoomManager.py b/transcendence/abstract/AbstractRoomManager.py new file mode 100644 index 0000000..58ad97f --- /dev/null +++ b/transcendence/abstract/AbstractRoomManager.py @@ -0,0 +1,18 @@ +from .AbstractRoom import AbstractRoom + +class AbstractRoomManager: + + def __init__(self): + self._room_list: [AbstractRoom] = [] + + def get(self, mode: int): + for room in self._room_list: + room: A + if (waiting_room._mode == mode): + return waiting_room + tmp: WaitingRoom = WaitingRoom(self, mode) + self._waiting_rooms.append(tmp) + return tmp + + def remove(self, waiting_room: WaitingRoom): + self._waiting_rooms.remove(waiting_room) \ No newline at end of file diff --git a/transcendence/abstract/AbstractRoomMember.py b/transcendence/abstract/AbstractRoomMember.py new file mode 100644 index 0000000..743ec60 --- /dev/null +++ b/transcendence/abstract/AbstractRoomMember.py @@ -0,0 +1,16 @@ +class AbstractRoomMember: + + def __init__(self, user_id: int, socket: WebsocketConsumer): + self.user_id: int = user_id + self.socket: WebsocketConsumer = socket + + def send(self, detail: str, data: dict = {}): + raw_data: dict = {"detail": detail} + raw_data.update(data) + self.socket.send(text_data=json.dumps(raw_data)) + + def accept(self): + self.socket.accept() + + def disconnect(self): + self.socket.disconnect(200) \ No newline at end of file diff --git a/trancendence/asgi.py b/transcendence/asgi.py similarity index 100% rename from trancendence/asgi.py rename to transcendence/asgi.py diff --git a/trancendence/settings.py b/transcendence/settings.py similarity index 95% rename from trancendence/settings.py rename to transcendence/settings.py index 3080286..b75cedb 100644 --- a/trancendence/settings.py +++ b/transcendence/settings.py @@ -1,5 +1,5 @@ """ -Django settings for trancendence project. +Django settings for transcendence project. Generated by 'django-admin startproject' using Django 4.2.6. @@ -61,7 +61,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', ] -ASGI_APPLICATION = 'trancendence.asgi.application' +ASGI_APPLICATION = 'transcendence.asgi.application' CHANNEL_LAYERS = { 'default' :{ @@ -81,7 +81,7 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] -ROOT_URLCONF = 'trancendence.urls' +ROOT_URLCONF = 'transcendence.urls' TEMPLATES = [ { @@ -99,7 +99,7 @@ TEMPLATES = [ }, ] -WSGI_APPLICATION = 'trancendence.wsgi.application' +WSGI_APPLICATION = 'transcendence.wsgi.application' # Database diff --git a/trancendence/urls.py b/transcendence/urls.py similarity index 100% rename from trancendence/urls.py rename to transcendence/urls.py diff --git a/trancendence/wsgi.py b/transcendence/wsgi.py similarity index 100% rename from trancendence/wsgi.py rename to transcendence/wsgi.py From 4e213471991a59c7110ba590a1abc49bc7267d1b Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 18:11:30 +0100 Subject: [PATCH 28/42] rename the project --- manage.py | 2 +- {trancendence => transcendence}/__init__.py | 0 transcendence/abstract/AbstractRoom.py | 53 +++++++++++++++++++ transcendence/abstract/AbstractRoomManager.py | 18 +++++++ transcendence/abstract/AbstractRoomMember.py | 16 ++++++ {trancendence => transcendence}/asgi.py | 0 {trancendence => transcendence}/settings.py | 8 +-- {trancendence => transcendence}/urls.py | 0 {trancendence => transcendence}/wsgi.py | 0 9 files changed, 92 insertions(+), 5 deletions(-) rename {trancendence => transcendence}/__init__.py (100%) create mode 100644 transcendence/abstract/AbstractRoom.py create mode 100644 transcendence/abstract/AbstractRoomManager.py create mode 100644 transcendence/abstract/AbstractRoomMember.py rename {trancendence => transcendence}/asgi.py (100%) rename {trancendence => transcendence}/settings.py (95%) rename {trancendence => transcendence}/urls.py (100%) rename {trancendence => transcendence}/wsgi.py (100%) diff --git a/manage.py b/manage.py index dd64115..9294fec 100755 --- a/manage.py +++ b/manage.py @@ -6,7 +6,7 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trancendence.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'transcendence.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/trancendence/__init__.py b/transcendence/__init__.py similarity index 100% rename from trancendence/__init__.py rename to transcendence/__init__.py diff --git a/transcendence/abstract/AbstractRoom.py b/transcendence/abstract/AbstractRoom.py new file mode 100644 index 0000000..2a2b439 --- /dev/null +++ b/transcendence/abstract/AbstractRoom.py @@ -0,0 +1,53 @@ +from .AbstractRoomMember import AbstractRoomMember + +class AbstractRoom: + + def __init__(self, room_manager): + self._member_list: [AbstractRoomMember] = [] + self.room_manager = room_manager + + def broadcast(self, detail: str, data: dict = {}): + for member in self._member_list: + member: AbstractRoomMember + member.send(detail, data) + + def clear(self): + self._member_list.clear() + + def get_member_by_socket(self, socket: WebsocketConsumer): + for member in self._member_list: + member: AbstractRoomMember + if (member.socket is socket): + return member + return None + + def get_member_by_user_id(self, user_id: int): + for member in self._member_list: + member: AbstractRoomMember + if (member.user_id == user_id): + return member + return None + + + def append(self, member: AbstractRoomMember): + tmp: AbstractRoomMember = self.get_member_by_user_id(member.user_id) + if (tmp is not None): + tmp.send("Connection close: Another connection open with the same user id.") + self.remove(tmp) + member.accept() + self._member_list.append(waiter) + + def remove(self, member: AbstractRoomMember): + self._member_list.remove(member) + waiter.disconnect() + + def empty(self): + for _ in self._waiter_list: + return False + return True + + def get_users_id(self): + return [waiter.user_id for waiter in self._waiter_list] + + def __len__(self): + return len(self._waiter_list) \ No newline at end of file diff --git a/transcendence/abstract/AbstractRoomManager.py b/transcendence/abstract/AbstractRoomManager.py new file mode 100644 index 0000000..58ad97f --- /dev/null +++ b/transcendence/abstract/AbstractRoomManager.py @@ -0,0 +1,18 @@ +from .AbstractRoom import AbstractRoom + +class AbstractRoomManager: + + def __init__(self): + self._room_list: [AbstractRoom] = [] + + def get(self, mode: int): + for room in self._room_list: + room: A + if (waiting_room._mode == mode): + return waiting_room + tmp: WaitingRoom = WaitingRoom(self, mode) + self._waiting_rooms.append(tmp) + return tmp + + def remove(self, waiting_room: WaitingRoom): + self._waiting_rooms.remove(waiting_room) \ No newline at end of file diff --git a/transcendence/abstract/AbstractRoomMember.py b/transcendence/abstract/AbstractRoomMember.py new file mode 100644 index 0000000..743ec60 --- /dev/null +++ b/transcendence/abstract/AbstractRoomMember.py @@ -0,0 +1,16 @@ +class AbstractRoomMember: + + def __init__(self, user_id: int, socket: WebsocketConsumer): + self.user_id: int = user_id + self.socket: WebsocketConsumer = socket + + def send(self, detail: str, data: dict = {}): + raw_data: dict = {"detail": detail} + raw_data.update(data) + self.socket.send(text_data=json.dumps(raw_data)) + + def accept(self): + self.socket.accept() + + def disconnect(self): + self.socket.disconnect(200) \ No newline at end of file diff --git a/trancendence/asgi.py b/transcendence/asgi.py similarity index 100% rename from trancendence/asgi.py rename to transcendence/asgi.py diff --git a/trancendence/settings.py b/transcendence/settings.py similarity index 95% rename from trancendence/settings.py rename to transcendence/settings.py index 3080286..b75cedb 100644 --- a/trancendence/settings.py +++ b/transcendence/settings.py @@ -1,5 +1,5 @@ """ -Django settings for trancendence project. +Django settings for transcendence project. Generated by 'django-admin startproject' using Django 4.2.6. @@ -61,7 +61,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', ] -ASGI_APPLICATION = 'trancendence.asgi.application' +ASGI_APPLICATION = 'transcendence.asgi.application' CHANNEL_LAYERS = { 'default' :{ @@ -81,7 +81,7 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] -ROOT_URLCONF = 'trancendence.urls' +ROOT_URLCONF = 'transcendence.urls' TEMPLATES = [ { @@ -99,7 +99,7 @@ TEMPLATES = [ }, ] -WSGI_APPLICATION = 'trancendence.wsgi.application' +WSGI_APPLICATION = 'transcendence.wsgi.application' # Database diff --git a/trancendence/urls.py b/transcendence/urls.py similarity index 100% rename from trancendence/urls.py rename to transcendence/urls.py diff --git a/trancendence/wsgi.py b/transcendence/wsgi.py similarity index 100% rename from trancendence/wsgi.py rename to transcendence/wsgi.py From 699abc9d1bc99114cad7165c4dc47cd6da836fca Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 18:30:45 +0100 Subject: [PATCH 29/42] core: use abstract room, member, roommanager --- matchmaking/consumers.py | 6 +- matchmaking/models.py | 91 ++++--------------- transcendence/abstract/AbstractRoom.py | 16 ++-- transcendence/abstract/AbstractRoomManager.py | 16 +--- transcendence/abstract/AbstractRoomMember.py | 4 + 5 files changed, 36 insertions(+), 97 deletions(-) diff --git a/matchmaking/consumers.py b/matchmaking/consumers.py index 791d6ef..2087c54 100644 --- a/matchmaking/consumers.py +++ b/matchmaking/consumers.py @@ -28,14 +28,14 @@ class MatchMaking(WebsocketConsumer): waiting_room: WaitingRoom = normal.get(self.mode) waiting_room.append(Waiter(user.pk, self)) - waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room._mode}") - if (len(waiting_room) == self.mode): + waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room.mode}") + if (len(waiting_room) == waiting_room.mode): game_id: int = GameModel().create(waiting_room.get_users_id()) waiting_room.broadcast("game_found", {"game_id": game_id}) waiting_room.clear() def disconnect(self, close_code): waiting_room: WaitingRoom = normal.get(self.mode) - waiter: Waiter = waiting_room.get_waiter_by_socket(self) + waiter: Waiter = waiting_room.get_member_by_socket(self) if (waiter is not None): waiting_room.remove(waiter) \ No newline at end of file diff --git a/matchmaking/models.py b/matchmaking/models.py index a1f3fc5..8ce235d 100644 --- a/matchmaking/models.py +++ b/matchmaking/models.py @@ -3,94 +3,37 @@ from django.db import models from channels.generic.websocket import WebsocketConsumer import json +from transcendence.abstract.AbstractRoom import AbstractRoom +from transcendence.abstract.AbstractRoomManager import AbstractRoomManager +from transcendence.abstract.AbstractRoomMember import AbstractRoomMember + # Create your models here. -class Waiter: +class Waiter(AbstractRoomMember): + pass - def __init__(self, user_id: int, socket: WebsocketConsumer): - self.user_id: int = user_id - self.socket: WebsocketConsumer = socket +class WaitingRoom(AbstractRoom): - def send(self, detail: str, data: dict = {}): - raw_data: dict = {"detail": detail} - raw_data.update(data) - self.socket.send(text_data=json.dumps(raw_data)) - - def accept(self): - self.socket.accept() - - def disconnect(self): - self.socket.disconnect(200) - -class WaitingRoom: - - def __init__(self, waiting_room_manager, mode: int): - self._waiter_list: [Waiter] = [] - self._mode: int = mode - self._waiting_room_manager = waiting_room_manager - - def broadcast(self, detail: str, data: dict = {}): - for waiter in self._waiter_list: - waiter: Waiter - waiter.send(detail, data) - - def clear(self): - self._waiter_list.clear() - - def get_waiter_by_socket(self, socket: WebsocketConsumer): - for waiter in self._waiter_list: - waiter: Waiter - if (waiter.socket is socket): - return waiter - return None - - def get_waiter_by_user_id(self, user_id: int): - for waiter in self._waiter_list: - waiter: Waiter - if (waiter.user_id == user_id): - return waiter - return None - + def __init__(self, room_manager,mode): + super().__init__(room_manager) + self.mode = mode def append(self, waiter: Waiter): - tmp: Waiter = self.get_waiter_by_user_id(waiter.user_id) + tmp: Waiter = self.get_member_by_user_id(waiter.user_id) if (tmp is not None): tmp.send("Connection close: Another connection open with the same user id.") self.remove(tmp) waiter.accept() - self._waiter_list.append(waiter) + self._member_list.append(waiter) - def remove(self, waiter: Waiter): - self._waiter_list.remove(waiter) - waiter.disconnect() - - def empty(self): - for _ in self._waiter_list: - return False - return True - - def get_users_id(self): - return [waiter.user_id for waiter in self._waiter_list] - - def __len__(self): - return len(self._waiter_list) - - - -class WaitingRoomManager: - - def __init__(self): - self._waiting_rooms: [WaitingRoom] = [] +class WaitingRoomManager(AbstractRoomManager): def get(self, mode: int): - for waiting_room in self._waiting_rooms: - waiting_room: WaitingRoom = waiting_room - if (waiting_room._mode == mode): + for waiting_room in self._room_list: + waiting_room: WaitingRoom + if (waiting_room.mode == mode): return waiting_room tmp: WaitingRoom = WaitingRoom(self, mode) - self._waiting_rooms.append(tmp) + super().append(tmp) return tmp - - def remove(self, waiting_room: WaitingRoom): - self._waiting_rooms.remove(waiting_room) normal: WaitingRoomManager = WaitingRoomManager() \ No newline at end of file diff --git a/transcendence/abstract/AbstractRoom.py b/transcendence/abstract/AbstractRoom.py index 2a2b439..aceb9af 100644 --- a/transcendence/abstract/AbstractRoom.py +++ b/transcendence/abstract/AbstractRoom.py @@ -1,3 +1,5 @@ +from channels.generic.websocket import WebsocketConsumer + from .AbstractRoomMember import AbstractRoomMember class AbstractRoom: @@ -30,24 +32,20 @@ class AbstractRoom: def append(self, member: AbstractRoomMember): - tmp: AbstractRoomMember = self.get_member_by_user_id(member.user_id) - if (tmp is not None): - tmp.send("Connection close: Another connection open with the same user id.") - self.remove(tmp) - member.accept() self._member_list.append(waiter) + member.accept() def remove(self, member: AbstractRoomMember): self._member_list.remove(member) - waiter.disconnect() + member.disconnect() def empty(self): - for _ in self._waiter_list: + for _ in self._member_list: return False return True def get_users_id(self): - return [waiter.user_id for waiter in self._waiter_list] + return [member.user_id for member in self._member_list] def __len__(self): - return len(self._waiter_list) \ No newline at end of file + return len(self._member_list) \ No newline at end of file diff --git a/transcendence/abstract/AbstractRoomManager.py b/transcendence/abstract/AbstractRoomManager.py index 58ad97f..578102d 100644 --- a/transcendence/abstract/AbstractRoomManager.py +++ b/transcendence/abstract/AbstractRoomManager.py @@ -5,14 +5,8 @@ class AbstractRoomManager: def __init__(self): self._room_list: [AbstractRoom] = [] - def get(self, mode: int): - for room in self._room_list: - room: A - if (waiting_room._mode == mode): - return waiting_room - tmp: WaitingRoom = WaitingRoom(self, mode) - self._waiting_rooms.append(tmp) - return tmp - - def remove(self, waiting_room: WaitingRoom): - self._waiting_rooms.remove(waiting_room) \ No newline at end of file + def append(self, room: AbstractRoom): + self._room_list.append(room) + + def remove(self, room: AbstractRoom): + self._room_list.remove(room) \ No newline at end of file diff --git a/transcendence/abstract/AbstractRoomMember.py b/transcendence/abstract/AbstractRoomMember.py index 743ec60..1e0b506 100644 --- a/transcendence/abstract/AbstractRoomMember.py +++ b/transcendence/abstract/AbstractRoomMember.py @@ -1,3 +1,7 @@ +from channels.generic.websocket import WebsocketConsumer + +import json + class AbstractRoomMember: def __init__(self, user_id: int, socket: WebsocketConsumer): From 58f6214ad090895927698538e9950cedd04a1de4 Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 18:34:18 +0100 Subject: [PATCH 30/42] fix: de golmon --- transcendence/abstract/AbstractRoom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transcendence/abstract/AbstractRoom.py b/transcendence/abstract/AbstractRoom.py index ddee137..d2c6502 100644 --- a/transcendence/abstract/AbstractRoom.py +++ b/transcendence/abstract/AbstractRoom.py @@ -40,7 +40,7 @@ class AbstractRoom: member.disconnect() def empty(self): - for _ in self._waiter_list: + for _ in self._member_list: return False return True From 88aca3a54b6cdaf9dc83f0df802e320d03cdc625 Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 18:36:16 +0100 Subject: [PATCH 31/42] fix: de golmon --- transcendence/abstract/AbstractRoomManager.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/transcendence/abstract/AbstractRoomManager.py b/transcendence/abstract/AbstractRoomManager.py index 2a2efe9..a4bf09a 100644 --- a/transcendence/abstract/AbstractRoomManager.py +++ b/transcendence/abstract/AbstractRoomManager.py @@ -5,14 +5,8 @@ class AbstractRoomManager: def __init__(self): self._room_list: [AbstractRoom] = [] - def get(self, mode: int): - for room in self._room_list: - room: A - if (waiting_room._mode == mode): - return waiting_room - tmp: WaitingRoom = WaitingRoom(self, mode) - self._waiting_rooms.append(tmp) - return tmp - - def remove(self, waiting_room: WaitingRoom): - self._waiting_rooms.remove(waiting_room) + def remove(self, room: AbstractRoom): + self._room_list.remove(room) + + def append(self, room: AbstractRoom): + self._room_list.append(room) From 397344ee4509c626dc70b4afc9fde0e2bc864dbe Mon Sep 17 00:00:00 2001 From: starnakin Date: Sat, 23 Dec 2023 18:47:16 +0100 Subject: [PATCH 32/42] matchmaking: add: clear message if normal closure --- frontend/static/js/api/matchmaking.js | 2 +- frontend/static/js/views/MatchMakingView.js | 4 +++- matchmaking/consumers.py | 2 +- transcendence/abstract/AbstractRoom.py | 4 ++-- transcendence/abstract/AbstractRoomMember.py | 4 ++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/static/js/api/matchmaking.js b/frontend/static/js/api/matchmaking.js index e33ecf9..9649236 100644 --- a/frontend/static/js/api/matchmaking.js +++ b/frontend/static/js/api/matchmaking.js @@ -39,7 +39,7 @@ class MatchMaking onclose(event) { this.stop(); - this.disconnect_func() + this.disconnect_func(event); } async stop() diff --git a/frontend/static/js/views/MatchMakingView.js b/frontend/static/js/views/MatchMakingView.js index bc47559..eb084d3 100644 --- a/frontend/static/js/views/MatchMakingView.js +++ b/frontend/static/js/views/MatchMakingView.js @@ -25,8 +25,10 @@ export default class extends AbstractAuthentifiedView { } } - ondisconnect() + ondisconnect(event) { + if (event.code === 1000) + clear("innerText", ["detail"]) document.getElementById("button").value = "Find a game" } diff --git a/matchmaking/consumers.py b/matchmaking/consumers.py index 2087c54..819ca40 100644 --- a/matchmaking/consumers.py +++ b/matchmaking/consumers.py @@ -38,4 +38,4 @@ class MatchMaking(WebsocketConsumer): waiting_room: WaitingRoom = normal.get(self.mode) waiter: Waiter = waiting_room.get_member_by_socket(self) if (waiter is not None): - waiting_room.remove(waiter) \ No newline at end of file + waiting_room.remove(waiter, 1016) \ No newline at end of file diff --git a/transcendence/abstract/AbstractRoom.py b/transcendence/abstract/AbstractRoom.py index d2c6502..09b202b 100644 --- a/transcendence/abstract/AbstractRoom.py +++ b/transcendence/abstract/AbstractRoom.py @@ -35,9 +35,9 @@ class AbstractRoom: self._member_list.append(waiter) member.accept() - def remove(self, member: AbstractRoomMember): + def remove(self, member: AbstractRoomMember, code: int = 1000): self._member_list.remove(member) - member.disconnect() + member.disconnect(code) def empty(self): for _ in self._member_list: diff --git a/transcendence/abstract/AbstractRoomMember.py b/transcendence/abstract/AbstractRoomMember.py index 1e0b506..0a89b98 100644 --- a/transcendence/abstract/AbstractRoomMember.py +++ b/transcendence/abstract/AbstractRoomMember.py @@ -16,5 +16,5 @@ class AbstractRoomMember: def accept(self): self.socket.accept() - def disconnect(self): - self.socket.disconnect(200) \ No newline at end of file + def disconnect(self, code: int = 1000): + self.socket.disconnect(code) \ No newline at end of file From 2932c2af1f587c7e7782d0ed14190849567df89a Mon Sep 17 00:00:00 2001 From: starnakin Date: Sun, 24 Dec 2023 15:37:31 +0100 Subject: [PATCH 33/42] abstract room: append: fix: use right var --- transcendence/abstract/AbstractRoom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transcendence/abstract/AbstractRoom.py b/transcendence/abstract/AbstractRoom.py index 09b202b..7c1c87f 100644 --- a/transcendence/abstract/AbstractRoom.py +++ b/transcendence/abstract/AbstractRoom.py @@ -32,7 +32,7 @@ class AbstractRoom: def append(self, member: AbstractRoomMember): - self._member_list.append(waiter) + self._member_list.append(member) member.accept() def remove(self, member: AbstractRoomMember, code: int = 1000): From 8ba55d5be2d9e71b6ee9fda66b1f76c3996455b9 Mon Sep 17 00:00:00 2001 From: starnakin Date: Sun, 24 Dec 2023 16:58:36 +0100 Subject: [PATCH 34/42] tournament: add: player can join tournament now --- .../static/js/api/tournament/tournament.js | 44 ++++++++++ .../static/js/views/TournamentPageView.js | 43 ++++++++-- tournament/consumers.py | 32 +++---- tournament/models.py | 86 ++++++++++++++++++- tournament/routing.py | 6 ++ transcendence/asgi.py | 4 +- 6 files changed, 191 insertions(+), 24 deletions(-) create mode 100644 tournament/routing.py diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index 5a4ca37..72e0cb4 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -20,6 +20,8 @@ class Tourmanent this.levels = levels; this.state = this.get_state(); this.id = id + + this.connected = false; } get_state() @@ -50,6 +52,48 @@ class Tourmanent this.levels = response_data.levels; this.id = response_data.id } + + leave(event) + { + if (this.connected == false) + return + this.connected = false; + this._socket.close() + this.disconnect_func(event); + } + + toggle_participation() + { + if (!this.connected) + return + console.log(this.isParticipating); + this.isParticipating = !this.isParticipating; + console.log(this.isParticipating); + this._socket.send(JSON.stringify({participate: this.isParticipating})); + } + + async join(receive_func, disconnect_func) + { + if (!await this.client.isAuthentificate()) + return null; + + let url = `wss://${window.location.host}/ws/tournaments/${this.id}`; + + this._socket = new WebSocket(url); + + this.connected = true; + this.isParticipating = false; + + this.receive_func = receive_func; + this.disconnect_func = disconnect_func; + + this._socket.onmessage = function (event) { + const data = JSON.parse(event.data); + receive_func(data); + }; + + this._socket.onclose = this.leave.bind(this); + } } diff --git a/frontend/static/js/views/TournamentPageView.js b/frontend/static/js/views/TournamentPageView.js index a7787af..18ae9c4 100644 --- a/frontend/static/js/views/TournamentPageView.js +++ b/frontend/static/js/views/TournamentPageView.js @@ -9,19 +9,43 @@ export default class extends AbstractAuthentifiedView this.id = params.id; } + pressButton() + { + this.tournament.toggle_participation(); + document.getElementById("button").value = this.tournament.isParticipating ? "Leave tournament" : "Join tournament"; + } + + async receive(data) + { + if (data.detail === "nb_participants" || data.detail === "update_participants") + document.getElementById("nb_participants").innerText = `${data.nb_participants} / ${this.tournament.nb_players}` + } + + async ondisconnect(event) + { + } + async postInit() { - let tournament = await client.tournaments.getTournament(this.id); + this.tournament = await client.tournaments.getTournament(this.id); - if (tournament === null) + if (this.tournament === null) return 1; - document.getElementById("name").innerText = tournament.name; - document.getElementById("nb_players").innerText = tournament.nb_players; - document.getElementById("nb_players_by_game").innerText = tournament.nb_players_by_game; - document.getElementById("level").innerText = tournament.level; - document.getElementById("state").innerText = tournament.state; + this.tournament.join(this.receive.bind(this), this.ondisconnect.bind(this)); + let button = document.getElementById("button") + + button.onclick = this.pressButton.bind(this); + + document.getElementById("name").innerText = this.tournament.name; + document.getElementById("nb_players").innerText = this.tournament.nb_players; + document.getElementById("nb_players_by_game").innerText = this.tournament.nb_players_by_game; + document.getElementById("level").innerText = this.tournament.level; + document.getElementById("state").innerText = this.tournament.state; + + if (this.tournament.state === "waiting") + button.disabled = false; } async getHtml() @@ -46,12 +70,17 @@ export default class extends AbstractAuthentifiedView Number of round Loading... + + Number of player + Loading... + status Loading... + ` } } diff --git a/tournament/consumers.py b/tournament/consumers.py index d021226..07aaee6 100644 --- a/tournament/consumers.py +++ b/tournament/consumers.py @@ -6,9 +6,9 @@ from games.models import GameModel import json -from .models import Waiter, WaitingRoom, WaitingRoomManager, normal +from .models import tournament_manager, TournamentMember, TournamentRoom, TournamentRoomManager -class TournamentWaitingRoom(WebsocketConsumer): +class TournamentWebConsumer(WebsocketConsumer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -17,25 +17,27 @@ class TournamentWaitingRoom(WebsocketConsumer): def connect(self): - user: User = self.scope["user"] - if (user.is_anonymous or not user.is_authenticated): + self.user: User = self.scope["user"] + if (self.user.is_anonymous or not self.user.is_authenticated): return self.channel_layer.group_add(self.group_name, self.channel_name) self.tournament_id = int(self.scope['url_route']['kwargs']['tournament_id']) - waiting_room: WaitingRoom = normal.get(self.mode) - waiting_room.append(Waiter(user.pk, self)) + self.room = tournament_manager.get(self.tournament_id) + self.participant = TournamentMember(self.user.pk, self, self.room) - print(len(waiting_room), "/", self.mode, [len(waiting_room),self.mode]) - if (len(waiting_room) == self.mode): - game_id: int = GameModel().create(waiting_room.get_users_id()) - waiting_room.broadcast({"detail": "Game found !", "game_id": game_id}) - waiting_room.clear() + if (self.room is None): + self.participant.send("Tournament not found") + self.disconnect(1017) + + self.room.append(self.participant) + + def receive(self, text_data: str = None, bytes_data: bytes = None): + self.participant.receive(text_data, bytes_data) def disconnect(self, close_code): - waiting_room: WaitingRoom = normal.get(self.mode) - waiter: Waiter = waiting_room.get_waiter_by_socket(self) - if (waiter is not None): - waiting_room.remove(self.scope["user"].pk) \ No newline at end of file + member = self.room.get_member_by_socket(self) + if (member is not None): + self.room.remove(self.participant, close_code) \ No newline at end of file diff --git a/tournament/models.py b/tournament/models.py index 287c929..117e92d 100644 --- a/tournament/models.py +++ b/tournament/models.py @@ -1,7 +1,15 @@ from django.db import models +from channels.generic.websocket import WebsocketConsumer + from games.models import GameModel +import json + +from transcendence.abstract.AbstractRoomMember import AbstractRoomMember +from transcendence.abstract.AbstractRoom import AbstractRoom +from transcendence.abstract.AbstractRoomManager import AbstractRoomManager + # Create your models here.tu class TournamentModel(models.Model): @@ -33,4 +41,80 @@ class TournamentGamesModel(models.Model): tournament_id = models.IntegerField() tournament_level = models.IntegerField() - game_id = models.IntegerField() \ No newline at end of file + game_id = models.IntegerField() + +class TournamentMember(AbstractRoomMember): + + def __init__(self, user_id: int, socket: WebsocketConsumer, tournament): + super().__init__(user_id, socket) + self.participate = False + self.tournament = tournament + + def receive(self, text_data: str = None, byte_dates: bytes = None): + + if (text_data is None): + return + + data: dict = json.loads(text_data) + + self.update_participate(data.get("participate", self.participate)) + + def update_participate(self, new_participate: bool): + + if (self.participate == new_participate): + return + self.participate = new_participate + self.tournament.update_participants() + +class TournamentRoom(AbstractRoom): + + def __init__(self, room_manager, tournament_id: int): + super().__init__(room_manager) + self.tournament_id = tournament_id + self.definitive_participant_list = [] + self.started = False + self.model = TournamentModel.objects.get(pk=tournament_id) + + def start(self): + self.broadcast("tournament_start") + + def update_participants(self): + nb_participants = self.get_nb_participants() + self.broadcast("update_participants", {"nb_participants": nb_participants}) + if (nb_participants == self.model.nb_players): + self.start() + + def get_nb_participants(self): + nb_participants = 0 + for member in self._member_list: + member: TournamentMember + if (member.participate): + nb_participants += 1 + return nb_participants + + def get_participants(self): + return [member.user_id for member in self._member_list if member.participate] + + def start(self): + self.started = True + + def append(self, member: TournamentMember): + super().append(member) + member.send("nb_participants", {"nb_participants": self.get_nb_participants()}) + +class TournamentRoomManager(AbstractRoomManager): + + def get(self, tournament_id: int): + + for room in self._room_list: + if (room.tournament_id == tournament_id): + return room + + if (TournamentModel.objects.filter(pk = tournament_id).exists()): + room = TournamentRoom(self, tournament_id) + self.append(room) + return room + + return None + +tournament_manager: TournamentRoomManager = TournamentRoomManager() \ No newline at end of file diff --git a/tournament/routing.py b/tournament/routing.py new file mode 100644 index 0000000..7271972 --- /dev/null +++ b/tournament/routing.py @@ -0,0 +1,6 @@ +from django.urls import re_path +from . import consumers + +websocket_urlpatterns = [ + re_path(r'ws/tournaments/(?P\d+)$', consumers.TournamentWebConsumer.as_asgi()) +] diff --git a/transcendence/asgi.py b/transcendence/asgi.py index fa7ffa8..939a8ed 100644 --- a/transcendence/asgi.py +++ b/transcendence/asgi.py @@ -13,6 +13,7 @@ from channels.auth import AuthMiddlewareStack import chat.routing import matchmaking.routing +import tournament.routing from django.core.asgi import get_asgi_application @@ -23,7 +24,8 @@ application = ProtocolTypeRouter({ 'websocket':AuthMiddlewareStack( URLRouter( chat.routing.websocket_urlpatterns + - matchmaking.routing.websocket_urlpatterns + matchmaking.routing.websocket_urlpatterns + + tournament.routing.websocket_urlpatterns ) ) }) From 52c7d9eafba29de9eac62997b4d7a5cf3abb16f9 Mon Sep 17 00:00:00 2001 From: AdrienLSH Date: Tue, 26 Dec 2023 01:03:09 +0100 Subject: [PATCH 35/42] add: websocket protocol now depends on http's --- frontend/static/js/api/chat/channel.js | 7 ++++++- frontend/static/js/api/matchmaking.js | 11 ++++++++--- frontend/static/js/api/tournament/tournament.js | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/frontend/static/js/api/chat/channel.js b/frontend/static/js/api/chat/channel.js index 5b36dd9..066c6ff 100644 --- a/frontend/static/js/api/chat/channel.js +++ b/frontend/static/js/api/chat/channel.js @@ -14,7 +14,12 @@ class Channel { // reload = function to use when we receive a message async connect(reload) { - let url = `wss://${window.location.host}/ws/chat/${this.channel_id}/`; + let url = ` + ${window.location.protocol == 'https' ? 'wss' : 'ws'} + ://${window.location.host} + /ws/chat/ + ${this.channel_id}/ + `; this.chatSocket = new WebSocket(url); this.chatSocket.onmessage = (event) =>{ diff --git a/frontend/static/js/api/matchmaking.js b/frontend/static/js/api/matchmaking.js index 9649236..0bd27ed 100644 --- a/frontend/static/js/api/matchmaking.js +++ b/frontend/static/js/api/matchmaking.js @@ -19,8 +19,13 @@ class MatchMaking if (!await this.client.isAuthentificate()) return null; - let url = `wss://${window.location.host}/ws/matchmaking/${mode}`; - + let url = ` + ${window.location.protocol == 'https' ? 'wss' : 'ws'} + ://${window.location.host} + /ws/matchmaking/ + ${mode} + `; + this._socket = new WebSocket(url); this.searching = true; @@ -49,4 +54,4 @@ class MatchMaking } } -export {MatchMaking} \ No newline at end of file +export {MatchMaking} diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index 72e0cb4..2c953db 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -77,7 +77,12 @@ class Tourmanent if (!await this.client.isAuthentificate()) return null; - let url = `wss://${window.location.host}/ws/tournaments/${this.id}`; + let url = ` + ${window.location.protocol == 'https' ? 'wss' : 'ws'} + ://${window.location.host} + /ws/tournaments/ + ${this.id} + `; this._socket = new WebSocket(url); @@ -97,4 +102,4 @@ class Tourmanent } -export { Tourmanent } \ No newline at end of file +export { Tourmanent } From efbcd10fb04847b4ec216932c69d4b2b97b00eac Mon Sep 17 00:00:00 2001 From: starnakin Date: Tue, 26 Dec 2023 14:28:04 +0100 Subject: [PATCH 36/42] fix: websocket url --- frontend/static/js/api/chat/channel.js | 7 +------ frontend/static/js/api/matchmaking.js | 7 +------ frontend/static/js/api/tournament/tournament.js | 10 ++++------ 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/frontend/static/js/api/chat/channel.js b/frontend/static/js/api/chat/channel.js index 066c6ff..22843b5 100644 --- a/frontend/static/js/api/chat/channel.js +++ b/frontend/static/js/api/chat/channel.js @@ -14,12 +14,7 @@ class Channel { // reload = function to use when we receive a message async connect(reload) { - let url = ` - ${window.location.protocol == 'https' ? 'wss' : 'ws'} - ://${window.location.host} - /ws/chat/ - ${this.channel_id}/ - `; + let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/chat/${this.channel_id}`; this.chatSocket = new WebSocket(url); this.chatSocket.onmessage = (event) =>{ diff --git a/frontend/static/js/api/matchmaking.js b/frontend/static/js/api/matchmaking.js index 0bd27ed..e55e48c 100644 --- a/frontend/static/js/api/matchmaking.js +++ b/frontend/static/js/api/matchmaking.js @@ -19,12 +19,7 @@ class MatchMaking if (!await this.client.isAuthentificate()) return null; - let url = ` - ${window.location.protocol == 'https' ? 'wss' : 'ws'} - ://${window.location.host} - /ws/matchmaking/ - ${mode} - `; + let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/matchmaking/${mode}`; this._socket = new WebSocket(url); diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index 2c953db..63afdaa 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -77,13 +77,11 @@ class Tourmanent if (!await this.client.isAuthentificate()) return null; - let url = ` - ${window.location.protocol == 'https' ? 'wss' : 'ws'} - ://${window.location.host} - /ws/tournaments/ - ${this.id} - `; + console.log(window.location.protocol); + let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/tournaments/${this.id}`; + console.log(url); + this._socket = new WebSocket(url); this.connected = true; From 0d926e78c159830f9c1c1e2e1b536a0eab08b437 Mon Sep 17 00:00:00 2001 From: starnakin Date: Tue, 26 Dec 2023 14:33:08 +0100 Subject: [PATCH 37/42] fix: chat url --- chat/routing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat/routing.py b/chat/routing.py index c799d8a..ddcf47d 100644 --- a/chat/routing.py +++ b/chat/routing.py @@ -2,5 +2,5 @@ from django.urls import re_path from . import consumers websocket_urlpatterns = [ - re_path(r'ws/chat/(?P\d+)/$', consumers.ChatConsumer.as_asgi()) + re_path(r'ws/chat/(?P\d+)$', consumers.ChatConsumer.as_asgi()) ] From 44fa12258588ab4a6047c0299f3f0ac5985f46f4 Mon Sep 17 00:00:00 2001 From: starnakin Date: Tue, 26 Dec 2023 14:35:45 +0100 Subject: [PATCH 38/42] clean: tournament: remove debug print --- frontend/static/js/api/tournament/tournament.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index 63afdaa..c0d783e 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -77,11 +77,8 @@ class Tourmanent if (!await this.client.isAuthentificate()) return null; - console.log(window.location.protocol); let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/tournaments/${this.id}`; - console.log(url); - this._socket = new WebSocket(url); this.connected = true; From 9bb6a32c70a189ff41313722a99917da583c6e2a Mon Sep 17 00:00:00 2001 From: starnakin Date: Tue, 26 Dec 2023 18:24:23 +0100 Subject: [PATCH 39/42] tournament: add: TOURNAMENT CAN START --- .../static/js/api/tournament/tournament.js | 6 +- .../static/js/views/TournamentPageView.js | 18 +++- games/serializers.py | 24 ++++++ tournament/consumers.py | 10 +-- tournament/models.py | 84 +++++++++++++------ tournament/serializers.py | 9 +- 6 files changed, 110 insertions(+), 41 deletions(-) create mode 100644 games/serializers.py diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index c0d783e..7e62508 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -51,6 +51,7 @@ class Tourmanent this.finished = response_data.finished; this.levels = response_data.levels; this.id = response_data.id + this.state = this.get_state(); } leave(event) @@ -66,10 +67,7 @@ class Tourmanent { if (!this.connected) return - console.log(this.isParticipating); - this.isParticipating = !this.isParticipating; - console.log(this.isParticipating); - this._socket.send(JSON.stringify({participate: this.isParticipating})); + this._socket.send(JSON.stringify({participate: ""})); } async join(receive_func, disconnect_func) diff --git a/frontend/static/js/views/TournamentPageView.js b/frontend/static/js/views/TournamentPageView.js index 18ae9c4..0e7d5c3 100644 --- a/frontend/static/js/views/TournamentPageView.js +++ b/frontend/static/js/views/TournamentPageView.js @@ -1,4 +1,4 @@ -import {client} from "../index.js"; +import {client, navigateTo} from "../index.js"; import AbstractAuthentifiedView from "./abstracts/AbstractAuthentifiedView.js"; export default class extends AbstractAuthentifiedView @@ -12,13 +12,24 @@ export default class extends AbstractAuthentifiedView pressButton() { this.tournament.toggle_participation(); - document.getElementById("button").value = this.tournament.isParticipating ? "Leave tournament" : "Join tournament"; } async receive(data) { if (data.detail === "nb_participants" || data.detail === "update_participants") document.getElementById("nb_participants").innerText = `${data.nb_participants} / ${this.tournament.nb_players}` + if (data.detail === "go_to") + navigateTo(data.url); + if (data.detail === "is_participant") + this.updateParticipating(data.is_participant) + if (data.detail === "error") + document.getElementById("display").innerText = data.error_message + } + + async updateParticipating(state) + { + document.getElementById("button").value = state ? `Leave ${this.tournament.name}` : `Join ${this.tournament.name}`; + document.getElementById("display").innerText = state ? "You are a particpant" : "You are not a participant"; } async ondisconnect(event) @@ -81,6 +92,7 @@ export default class extends AbstractAuthentifiedView - ` + + ` } } diff --git a/games/serializers.py b/games/serializers.py new file mode 100644 index 0000000..5bd2fc6 --- /dev/null +++ b/games/serializers.py @@ -0,0 +1,24 @@ +from rest_framework import serializers +from .models import GameModel, GameMembersModel + +class GameSerializer(serializers.ModelSerializer): + + players_id = serializers.SerializerMethodField() + winner_id = serializers.ReadOnlyField() + state = serializers.SerializerMethodField() + started = serializers.ReadOnlyField() + finished = serializers.ReadOnlyField() + + class Meta: + model = GameModel + fields = ["id", "winner_id", "state", "started", "finished", "players_id"] + + def get_state(self, instance: GameModel): + if (instance.finished): + return "finished" + if (instance.started): + return "started" + return "waiting" + + def get_players_id(self, instance: GameModel): + players_id = [player_game.member_id for player_game in GameMembersModel.objects.filter(game_id=instance.pk)] \ No newline at end of file diff --git a/tournament/consumers.py b/tournament/consumers.py index 07aaee6..ea2bb46 100644 --- a/tournament/consumers.py +++ b/tournament/consumers.py @@ -26,18 +26,18 @@ class TournamentWebConsumer(WebsocketConsumer): self.tournament_id = int(self.scope['url_route']['kwargs']['tournament_id']) self.room = tournament_manager.get(self.tournament_id) - self.participant = TournamentMember(self.user.pk, self, self.room) + self.member = TournamentMember(self.user.pk, self, self.room) if (self.room is None): - self.participant.send("Tournament not found") + self.member.send("Tournament not found") self.disconnect(1017) - self.room.append(self.participant) + self.room.append(self.member) def receive(self, text_data: str = None, bytes_data: bytes = None): - self.participant.receive(text_data, bytes_data) + self.member.receive(text_data, bytes_data) def disconnect(self, close_code): member = self.room.get_member_by_socket(self) if (member is not None): - self.room.remove(self.participant, close_code) \ No newline at end of file + self.room.remove(self.member, close_code) \ No newline at end of file diff --git a/tournament/models.py b/tournament/models.py index 117e92d..81ee3dc 100644 --- a/tournament/models.py +++ b/tournament/models.py @@ -20,22 +20,41 @@ class TournamentModel(models.Model): started = models.BooleanField(default=False) finished = models.BooleanField(default=False) - def create_game(self, users_id): - game_id = GameModel.create(users_id=users_id) - TournamentGamesModel(game_id=game_id, tournament_id=self.pk).save() + def create_game(self, level, users_id): + game_id = GameModel().create(users_id=users_id) + TournamentGamesModel(game_id=game_id, tournament_id=self.pk, tournament_level=level).save() return game_id def get_games_id_by_level(self, level): - return list(TournamentGamesModel.objects.filter(tournament_id=self.pk, tournament_level=level)) + tmp = TournamentGamesModel.objects.filter(tournament_id=self.pk, tournament_level=level) + return [instance.game_id for instance in tmp] def get_games_id(self): return list(TournamentGamesModel.objects.filter(tournament_id=self.pk)) def get_players_id(self): - lst: [int] = [] - for game_id in self.get_games_id(): - lst.append(GameMembersModel.objects.filter(game_id=game_id)) - return lst + return [model.participant_id for model in TournamentParticipantsModel.objects.filter(tournament_id=self.pk)] + + def is_a_participant(self, participant_id: int): + return TournamentParticipantsModel.objects.filter(participant_id=participant_id, tournament_id=self.pk).exists() + + def add_participants(self, participants_id: [int]): + for participant_id in participants_id: + TournamentParticipantsModel(tournament_id=self.pk, participant_id=participant_id).save() + + def start(self, participants_id: [int]): + self.started = True + self.add_participants(participants_id) + games_id = [int] + for i in range(0, len(participants_id), self.nb_players_by_game): + game_id = self.create_game(0, participants_id[i : i + self.nb_players_by_game]) + games_id.append(game_id) + self.save() + return games_id + +class TournamentParticipantsModel(models.Model): + tournament_id = models.IntegerField() + participant_id = models.IntegerField() class TournamentGamesModel(models.Model): @@ -45,10 +64,10 @@ class TournamentGamesModel(models.Model): class TournamentMember(AbstractRoomMember): - def __init__(self, user_id: int, socket: WebsocketConsumer, tournament): + def __init__(self, user_id: int, socket: WebsocketConsumer, room): super().__init__(user_id, socket) self.participate = False - self.tournament = tournament + self.room = room def receive(self, text_data: str = None, byte_dates: bytes = None): @@ -57,34 +76,46 @@ class TournamentMember(AbstractRoomMember): data: dict = json.loads(text_data) - self.update_participate(data.get("participate", self.participate)) + if (data.get("participate") is not None): + self.room.update_participants(self) - def update_participate(self, new_participate: bool): + def send_error_message(self, message: str): + self.send("error", {"error_message": message}) - if (self.participate == new_participate): - return - self.participate = new_participate - self.tournament.update_participants() + def go_to(self, url: str): + self.send("go_to", {"url": url}) + + def send_participating(self): + self.send("is_participant", {"is_participant": self.participate}) class TournamentRoom(AbstractRoom): def __init__(self, room_manager, tournament_id: int): super().__init__(room_manager) self.tournament_id = tournament_id - self.definitive_participant_list = [] - self.started = False - self.model = TournamentModel.objects.get(pk=tournament_id) + self.tournament = TournamentModel.objects.get(pk=tournament_id) def start(self): self.broadcast("tournament_start") + games_id = self.tournament.start(self.get_participants_id()) + for i, participant in enumerate(self.get_participants()): + participant: TournamentMember + participant.go_to(f"games/{games_id[i // self.tournament.nb_players_by_game]}") - def update_participants(self): + def update_participants(self, member: TournamentMember): + if (self.tournament.started): + member.send_error_message("Tournament already started") + return + member.participate = not member.participate nb_participants = self.get_nb_participants() self.broadcast("update_participants", {"nb_participants": nb_participants}) - if (nb_participants == self.model.nb_players): + member.send_participating() + if (nb_participants == self.tournament.nb_players): self.start() def get_nb_participants(self): + if (self.tournament.started): + return self.tournament.nb_players nb_participants = 0 for member in self._member_list: member: TournamentMember @@ -93,13 +124,16 @@ class TournamentRoom(AbstractRoom): return nb_participants def get_participants(self): + return [member for member in self._member_list if member.participate] + + def get_participants_id(self): return [member.user_id for member in self._member_list if member.participate] - def start(self): - self.started = True - def append(self, member: TournamentMember): super().append(member) + if self.tournament.started: + member.participate = self.tournament.is_a_participant(member.user_id) + member.send_participating() member.send("nb_participants", {"nb_participants": self.get_nb_participants()}) class TournamentRoomManager(AbstractRoomManager): @@ -110,7 +144,7 @@ class TournamentRoomManager(AbstractRoomManager): if (room.tournament_id == tournament_id): return room - if (TournamentModel.objects.filter(pk = tournament_id).exists()): + if (TournamentModel.objects.filter(pk = tournament_id, finished=False).exists()): room = TournamentRoom(self, tournament_id) self.append(room) return room diff --git a/tournament/serializers.py b/tournament/serializers.py index c17525b..7e06053 100644 --- a/tournament/serializers.py +++ b/tournament/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers from .models import TournamentModel +from games.serializers import GameSerializer class TournamentSerializer(serializers.ModelSerializer): @@ -13,13 +14,13 @@ class TournamentSerializer(serializers.ModelSerializer): model = TournamentModel fields = ["name", "nb_players", "nb_players_by_game", "level", "started", "finished", "levels", "id"] - def get_levels(self, instance): + def get_levels(self, instance: TournamentModel): levels: [[int]] = [] for i in range(instance.level): - level: [int] = instance.get_games_id_by_level(i) - if (level == []): + games_id: [int] = instance.get_games_id_by_level(i) + if (games_id == []): break - levels.append(level) + levels.append(games_id) return levels def validate_nb_players(self, value: int): From 98ec43e7b98c61db5314cb1c6d0789486de042d1 Mon Sep 17 00:00:00 2001 From: starnakin Date: Tue, 26 Dec 2023 21:32:20 +0100 Subject: [PATCH 40/42] game: core: rename member to player --- games/models.py | 12 ++++++------ tournament/models.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/games/models.py b/games/models.py index 514e52c..81c2d60 100644 --- a/games/models.py +++ b/games/models.py @@ -7,15 +7,15 @@ class GameModel(models.Model): started = models.BooleanField(default=False) winner_id = models.IntegerField(default=-1) - def create(self, users_id: [int]): + def create(self, players_id: [int]): self.save() - for user_id in users_id: - GameMembersModel(game_id=self.pk, member_id=user_id).save() + for player_id in players_id: + GameMembersModel(game_id = self.pk, player_id = player_id).save() return self.pk - def get_users(self): - return list(GameMembersModel.objects.filter(self.pk)) + def get_players_id(self): + return [game_member.member_id for game_member in GameMembersModel.objects.filter(self.pk)] class GameMembersModel(models.Model): game_id = models.IntegerField() - member_id = models.IntegerField() \ No newline at end of file + player_id = models.IntegerField() \ No newline at end of file diff --git a/tournament/models.py b/tournament/models.py index 81ee3dc..8ab156c 100644 --- a/tournament/models.py +++ b/tournament/models.py @@ -20,17 +20,17 @@ class TournamentModel(models.Model): started = models.BooleanField(default=False) finished = models.BooleanField(default=False) - def create_game(self, level, users_id): - game_id = GameModel().create(users_id=users_id) - TournamentGamesModel(game_id=game_id, tournament_id=self.pk, tournament_level=level).save() + def create_game(self, level, players_id): + game_id = GameModel().create(players_id = players_id) + TournamentGamesModel(game_id = game_id, tournament_id = self.pk, tournament_level = level).save() return game_id def get_games_id_by_level(self, level): - tmp = TournamentGamesModel.objects.filter(tournament_id=self.pk, tournament_level=level) + tmp = TournamentGamesModel.objects.filter(tournament_id = self.pk, tournament_level = level) return [instance.game_id for instance in tmp] def get_games_id(self): - return list(TournamentGamesModel.objects.filter(tournament_id=self.pk)) + return [tournament_game.game_id for tournament_game in TournamentGamesModel.objects.filter(tournament_id = self.pk)] def get_players_id(self): return [model.participant_id for model in TournamentParticipantsModel.objects.filter(tournament_id=self.pk)] From 35a2f273f97677f1a5a4e89b242bb476e22db2be Mon Sep 17 00:00:00 2001 From: starnakin Date: Tue, 26 Dec 2023 21:35:08 +0100 Subject: [PATCH 41/42] fix --- games/models.py | 8 ++++---- tournament/models.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/games/models.py b/games/models.py index 81c2d60..36d5394 100644 --- a/games/models.py +++ b/games/models.py @@ -3,9 +3,9 @@ from django.db import models # Create your models here. class GameModel(models.Model): - finished = models.BooleanField(default=False) - started = models.BooleanField(default=False) - winner_id = models.IntegerField(default=-1) + finished = models.BooleanField(default = False) + started = models.BooleanField(default = False) + winner_id = models.IntegerField(default = -1) def create(self, players_id: [int]): self.save() @@ -14,7 +14,7 @@ class GameModel(models.Model): return self.pk def get_players_id(self): - return [game_member.member_id for game_member in GameMembersModel.objects.filter(self.pk)] + return [game_member.member_id for game_member in GameMembersModel.objects.filter(game_id = self.pk)] class GameMembersModel(models.Model): game_id = models.IntegerField() diff --git a/tournament/models.py b/tournament/models.py index 8ab156c..912ec62 100644 --- a/tournament/models.py +++ b/tournament/models.py @@ -13,12 +13,12 @@ from transcendence.abstract.AbstractRoomManager import AbstractRoomManager # Create your models here.tu class TournamentModel(models.Model): - name = models.CharField(max_length=100) + name = models.CharField(max_length = 100) nb_players = models.IntegerField() nb_players_by_game = models.IntegerField() level = models.IntegerField() - started = models.BooleanField(default=False) - finished = models.BooleanField(default=False) + started = models.BooleanField(default = False) + finished = models.BooleanField(default = False) def create_game(self, level, players_id): game_id = GameModel().create(players_id = players_id) @@ -36,11 +36,11 @@ class TournamentModel(models.Model): return [model.participant_id for model in TournamentParticipantsModel.objects.filter(tournament_id=self.pk)] def is_a_participant(self, participant_id: int): - return TournamentParticipantsModel.objects.filter(participant_id=participant_id, tournament_id=self.pk).exists() + return TournamentParticipantsModel.objects.filter(participant_id = participant_id, tournament_id = self.pk).exists() def add_participants(self, participants_id: [int]): for participant_id in participants_id: - TournamentParticipantsModel(tournament_id=self.pk, participant_id=participant_id).save() + TournamentParticipantsModel(tournament_id = self.pk, participant_id = participant_id).save() def start(self, participants_id: [int]): self.started = True @@ -93,7 +93,7 @@ class TournamentRoom(AbstractRoom): def __init__(self, room_manager, tournament_id: int): super().__init__(room_manager) self.tournament_id = tournament_id - self.tournament = TournamentModel.objects.get(pk=tournament_id) + self.tournament = TournamentModel.objects.get(pk = tournament_id) def start(self): self.broadcast("tournament_start") @@ -144,7 +144,7 @@ class TournamentRoomManager(AbstractRoomManager): if (room.tournament_id == tournament_id): return room - if (TournamentModel.objects.filter(pk = tournament_id, finished=False).exists()): + if (TournamentModel.objects.filter(pk = tournament_id).exists()): room = TournamentRoom(self, tournament_id) self.append(room) return room From 9f7b813292405661811887865e63da1b7acf9118 Mon Sep 17 00:00:00 2001 From: starnakin Date: Wed, 27 Dec 2023 16:14:39 +0100 Subject: [PATCH 42/42] chat: fix and opti --- chat/admin.py | 8 +- chat/consumers.py | 55 +++++++------- chat/models.py | 17 +++-- chat/serializers.py | 30 +++++++- chat/tests.py | 27 +++++++ chat/urls.py | 3 +- chat/views.py | 98 ++++++++----------------- frontend/static/js/api/chat/channel.js | 4 +- frontend/static/js/api/chat/channels.js | 17 +++-- tournament/serializers.py | 1 - 10 files changed, 144 insertions(+), 116 deletions(-) diff --git a/chat/admin.py b/chat/admin.py index 8f38a1d..2778881 100644 --- a/chat/admin.py +++ b/chat/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import ChannelModel, MemberModel, MessageModel +from .models import ChatChannelModel, ChatMemberModel, ChatMessageModel -admin.site.register(ChannelModel) -admin.site.register(MemberModel) -admin.site.register(MessageModel) +admin.site.register(ChatChannelModel) +admin.site.register(ChatMemberModel) +admin.site.register(ChatMessageModel) diff --git a/chat/consumers.py b/chat/consumers.py index 9f07678..9ac7d9d 100644 --- a/chat/consumers.py +++ b/chat/consumers.py @@ -1,21 +1,25 @@ -import json from channels.generic.websocket import WebsocketConsumer from asgiref.sync import async_to_sync -from .models import MemberModel, MessageModel + +from .models import ChatMemberModel, ChatMessageModel from profiles.models import BlockModel + import time +import json class ChatConsumer(WebsocketConsumer): + def connect(self): - channel_id : str = self.scope['path'].split('/')[3] - - self.room_group_name = 'chat' + channel_id - + user = self.scope["user"] if (user.is_anonymous or not user.is_authenticated): return - if MemberModel.objects.filter(member_id=user.pk, channel_id=int(channel_id)).count() != 1: + channel_id : int = int(self.scope['url_route']['kwargs']['chat_id']) + + self.room_group_name = f'chat{channel_id}' + + if ChatMemberModel.objects.filter(member_id=user.pk, channel_id=int(channel_id)).count() != 1: return if (self.channel_layer == None): @@ -30,27 +34,33 @@ class ChatConsumer(WebsocketConsumer): def receive(self, text_data=None, bytes_data=None): + if text_data == None: return - text_data_json = json.loads(text_data) - message = text_data_json['message'] - receivers_id = text_data_json['receivers_id'] - - print(text_data) - - channel_id : int = int(self.scope['path'].split('/')[3]) user = self.scope["user"] if (user.is_anonymous or not user.is_authenticated): return - if MemberModel.objects.filter(member_id=user.pk, channel_id=channel_id).count() != 1: + text_data_json: dict = json.loads(text_data) + + message = text_data_json.get('message') + if (message is None): + return + + receivers_id = text_data_json.get('receivers_id') + if (receivers_id is None): + return + + channel_id : int = int(self.scope['url_route']['kwargs']['chat_id']) + + if ChatMemberModel.objects.filter(member_id = user.pk, channel_id = channel_id).count() != 1: return if (self.channel_layer == None): return - message_time : int = int(time.time() * 1000) + message_time: int = int(time.time() * 1000) if (len(receivers_id) == 1 and BlockModel.objects.filter(blocker=user.pk, blocked=receivers_id[0]) or @@ -68,22 +78,17 @@ class ChatConsumer(WebsocketConsumer): } ) - new_message = MessageModel() - new_message.channel_id = channel_id - new_message.author_id = user.pk - new_message.content = message - new_message.time = message_time - new_message.save() - + new_message = ChatMessageModel(channel_id = channel_id, author_id = user.pk, content = message, time = message_time).save() def chat_message(self, event): - channel_id : int = int(self.scope['path'].split('/')[3]) user = self.scope["user"] if (user.is_anonymous or not user.is_authenticated): return - if MemberModel.objects.filter(member_id=user.pk, channel_id=channel_id).count() != 1: + channel_id : int = int(self.scope['url_route']['kwargs']['chat_id']) + + if ChatMemberModel.objects.filter(member_id = user.pk, channel_id = channel_id).count() != 1: return self.send(text_data=json.dumps({ diff --git a/chat/models.py b/chat/models.py index bc72b90..852ced7 100644 --- a/chat/models.py +++ b/chat/models.py @@ -4,21 +4,28 @@ from django.contrib.auth.models import User from django.contrib import admin # Create your models here. -class ChannelModel(models.Model): - pass +class ChatChannelModel(models.Model): + + def create(self, users_id: [int]): + self.save() + for user_id in users_id: + ChatMemberModel(channel_id = self.pk, member_id = user_id).save() -class MemberModel(models.Model): + def get_members_id(self): + return [member_channel.member_id for member_channel in ChatMemberModel.objects.filter(channel_id = self.pk)] + +class ChatMemberModel(models.Model): member_id = IntegerField(primary_key=False) channel_id = IntegerField(primary_key=False) def __str__(self): return "member_id: " + str(self.member_id) + ", channel_id: " + str(self.channel_id) -class MessageModel(models.Model): +class ChatMessageModel(models.Model): channel_id = IntegerField(primary_key=False) author_id = IntegerField(primary_key=False) content = models.CharField(max_length=255) time = IntegerField(primary_key=False) def __str__(self): - return "author_id: " + str(self.author_id) + ", channel_id: " + str(self.channel_id) + ", content: " + self.content + return "author_id: " + str(self.author_id) + ", channel_id: " + str(self.channel_id) + ", content: " + self.content \ No newline at end of file diff --git a/chat/serializers.py b/chat/serializers.py index 2d62490..3eedadf 100644 --- a/chat/serializers.py +++ b/chat/serializers.py @@ -1,8 +1,30 @@ from rest_framework import serializers -from .models import ChannelModel -class ChannelSerializer(serializers.ModelSerializer): +from profiles.models import ProfileModel + +from .models import ChatChannelModel, ChatMessageModel + +class ChatChannelSerializer(serializers.ModelSerializer): + + members_id = serializers.ListField(child = serializers.IntegerField()) class Meta: - model = ChannelModel - fields = [] + model = ChatChannelModel + fields = ["members_id", "pk"] + + def validate_members_id(self, value): + members_id: [int] = value + if len(members_id) < 2: + raise serializers.ValidationError('Not enought members to create the channel') + if len(set(members_id)) != len(members_id): + raise serializers.ValidationError('Same member') + for member_id in members_id: + if not ProfileModel.objects.filter(pk = member_id).exists(): + raise serializers.ValidationError(f"The profile {member_id} doesn't exists.") + return members_id + +class ChatMessageSerializer(serializers.Serializer): + + class Meta: + model = ChatMessageModel + fields = ["channel_id", "author_id", "content", "time"] \ No newline at end of file diff --git a/chat/tests.py b/chat/tests.py index 7ce503c..cf0ee9f 100644 --- a/chat/tests.py +++ b/chat/tests.py @@ -1,3 +1,30 @@ from django.test import TestCase +from django.test.client import Client +from django.http import HttpResponse, HttpRequest +from django.contrib.auth.models import User + # Create your tests here. +class ChatTest(TestCase): + def setUp(self): + self.client = Client() + + self.username='bozo1' + self.password='password' + + self.user: User = User.objects.create_user(username=self.username, password=self.password) + + self.dest: User = User.objects.create_user(username="bozo2", password=self.password) + + self.url = "/api/chat/" + + def test_create_chat(self): + self.client.login(username=self.username, password=self.password) + response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]}) + response_dict: dict = eval(response.content) + self.assertDictEqual(response_dict, {}) + + def test_create_chat_unlogged(self): + response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]}) + response_dict: dict = eval(response.content) + self.assertDictEqual(response_dict, {'detail': 'Authentication credentials were not provided.'}) diff --git a/chat/urls.py b/chat/urls.py index f6a905e..4dce2f3 100644 --- a/chat/urls.py +++ b/chat/urls.py @@ -5,6 +5,5 @@ from django.conf.urls.static import static from . import views urlpatterns = [ - path("", views.ChatView.as_view(), name="chat_page"), - path("", views.ChatsView.as_view(), name="chats_page"), + path("", views.ChannelView.as_view(), name="chats_page"), ] diff --git a/chat/views.py b/chat/views.py index 23e51ea..bdee932 100644 --- a/chat/views.py +++ b/chat/views.py @@ -1,79 +1,45 @@ from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework import authentication, permissions, status +from rest_framework import permissions, status from rest_framework.authentication import SessionAuthentication -from .models import ChannelModel, MemberModel, MessageModel + +from django.http import HttpRequest +from django.contrib.auth import login +from django.db.models import QuerySet from django.core import serializers -class ChatView(APIView): + +from .models import ChatChannelModel, ChatMemberModel, ChatMessageModel +from .serializers import ChatChannelSerializer, ChatMessageSerializer + +class ChannelView(APIView): + + queryset = ChatChannelModel.objects + serializer_class = ChatChannelSerializer permission_classes = (permissions.IsAuthenticated,) - authentication_classes = (SessionAuthentication,) - - def get(self, request, pk): - if (ChannelModel.objects.filter(pk=pk)): - return Response({'channel_id': pk}, status=status.HTTP_200_OK) - else: - return Response("Channel doesn't exist", status=status.HTTP_404_NOT_FOUND) + authentication_classes = (SessionAuthentication,) - def delete(self, request, pk): - - ChannelModel.objects.filter(pk=pk).delete() - MessageModel.objects.filter(pk=pk).delete() - MemberModel.objects.filter(pk=pk).delete() - - return Response({'channel_id': pk}, status=status.HTTP_200_OK) - -class ChatsView(APIView): def post(self, request): - data: dict = request.data - users_id = request.data.get("users_id", []) - if len(users_id) < 2: - return Response('Not enought members to create the channel', status=status.HTTP_400_BAD_REQUEST) - if users_id[0] == users_id[1]: - return Response('Same member', status=status.HTTP_400_BAD_REQUEST) + serializer = self.serializer_class(data = request.data) + + serializer.is_valid(raise_exception=True) - for user_id1 in users_id: - for member1 in MemberModel.objects.filter(member_id=user_id1): - for user_id2 in users_id: - if user_id1 == user_id2: - continue - for member2 in MemberModel.objects.filter(member_id=user_id2): - if (member1.channel_id == member2.channel_id): - messages = MessageModel.objects.filter(channel_id=member1.channel_id).order_by("time") - messages = serializers.serialize("json", messages) - return Response({'channel_id': member1.channel_id, 'messages':messages}, status=status.HTTP_200_OK) - - new_channel = ChannelModel() - new_channel.save() + data: dict = serializer.validated_data - for user_id in users_id: + members_id = data.get("members_id") + if self.request.user.pk not in members_id: + return Response({"detail": "You are trying to create a chat group without you."}, status = status.HTTP_400_BAD_REQUEST) - new_member = MemberModel() - new_member.channel_id = new_channel.pk - new_member.member_id = user_id - new_member.save() + for member_channel in ChatMemberModel.objects.filter(member_id = members_id[0]): + channel_id: int = member_channel.channel_id + if not ChatChannelModel.objects.filter(pk = channel_id).exists(): + continue + channel: ChatChannelModel = ChatChannelModel.objects.get(pk = channel_id) + if set(channel.get_members_id()) == set(members_id): + messages = ChatMessageModel.objects.filter(channel_id = channel_id).order_by("time") + messages = serializers.serialize("json", messages) + return Response({'channel_id': channel_id, 'messages': messages}, status=status.HTTP_200_OK) - return Response({'channel_id': new_channel.pk}, status=status.HTTP_201_CREATED) - - def delete(self, request): - - data: dict = request.data - users_id = request.data.get("users_id", []) - - #print(list(MemberModel.objects.all())) - - for user_id1 in users_id: - for member1 in MemberModel.objects.filter(member_id=user_id1): - for user_id2 in users_id: - if user_id1 == user_id2: - break - for member2 in MemberModel.objects.filter(member_id=user_id2): - if (member1.channel_id == member2.channel_id): - MessageModel.objects.filter(channel_id=member1.channel_id).delete() - member1.delete() - member2.delete() - ChannelModel.objects.get(pk=member1.channel_id).delete() - return Response("Channel removed", status=status.HTTP_200_OK) - - return Response("Channel doesn't exist", status=status.HTTP_404_NOT_FOUND) + new_channel_id = ChatChannelModel().create(members_id) + return Response({'channel_id': new_channel_id}, status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/frontend/static/js/api/chat/channel.js b/frontend/static/js/api/chat/channel.js index 32f63f9..881b0bd 100644 --- a/frontend/static/js/api/chat/channel.js +++ b/frontend/static/js/api/chat/channel.js @@ -35,7 +35,9 @@ class Channel { this.chatSocket.close(); } - updateMessages(messages) { + updateMessages(messages) + { + console.log(messages); messages = JSON.parse(messages); let new_messages = []; diff --git a/frontend/static/js/api/chat/channels.js b/frontend/static/js/api/chat/channels.js index 5fad6fc..4d6bcc4 100644 --- a/frontend/static/js/api/chat/channels.js +++ b/frontend/static/js/api/chat/channels.js @@ -6,18 +6,18 @@ class Channels { this.client = client; } - async createChannel(users_id, reload) { + async createChannel(members_id, reload) { let null_id = false; - users_id.forEach(user_id => { - if (user_id == null) + members_id.forEach(member_id => { + if (member_id == null) null_id = true; }); if (null_id) - return console.log(users_id, "createChannel error, null id;"); + return console.log(members_id, "createChannel error, null id;"); let response = await this.client._post("/api/chat/", { - users_id:users_id + members_id:members_id }); let data = await response.json(); @@ -28,12 +28,13 @@ class Channels { let messages = undefined; if (exit_code == 200) messages = data.messages; - return new Channel(this.client, data.channel_id, users_id, messages, reload); + + return new Channel(this.client, data.channel_id, members_id, messages, reload); } - async deleteChannel(users_id) { + async deleteChannel(members_id) { let response = await this.client._delete("/api/chat/", { - users_id:users_id + members_id:members_id }); let data = await response.json(); diff --git a/tournament/serializers.py b/tournament/serializers.py index 7e06053..c1aba61 100644 --- a/tournament/serializers.py +++ b/tournament/serializers.py @@ -27,7 +27,6 @@ class TournamentSerializer(serializers.ModelSerializer): if (value < 2): raise serializers.ValidationError("The numbers of players must be greather than 2.") return value - def validate_nb_players_by_game(self, value: int): if (value < 2): raise serializers.ValidationError("The numbers of players by game must be greather than 2.")