diff --git a/chess_backend/asgi.py b/chess_backend/asgi.py index be4c508..d8f53d7 100644 --- a/chess_backend/asgi.py +++ b/chess_backend/asgi.py @@ -1,29 +1,18 @@ -""" -ASGI config for chess_backend project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ -""" - import os -from channels.auth import AuthMiddlewareStack -from channels.security.websocket import OriginValidator from django.core.asgi import get_asgi_application from channels.routing import ProtocolTypeRouter, URLRouter import room.routing +from room.middleware import HeaderAuthMiddleware os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chess_backend.settings") application = ProtocolTypeRouter( { "http": get_asgi_application(), - "websocket": OriginValidator( - AuthMiddlewareStack(URLRouter(room.routing.websocket_urlpatterns)), - ["*"], + "websocket": HeaderAuthMiddleware( + URLRouter(room.routing.websocket_urlpatterns) ), } ) diff --git a/chess_backend/settings.py b/chess_backend/settings.py index d332280..2e204bc 100644 --- a/chess_backend/settings.py +++ b/chess_backend/settings.py @@ -21,10 +21,12 @@ INSTALLED_APPS = [ "channels", # Apps "game", + "room" ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.messages.middleware.MessageMiddleware", diff --git a/game/admin.py b/game/admin.py deleted file mode 100644 index b8635f5..0000000 --- a/game/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin - -# Register your models here. -from game.models import HeroImageSet - -admin.site.register(HeroImageSet) diff --git a/game/models.py b/game/models.py index 954a3f2..3896378 100644 --- a/game/models.py +++ b/game/models.py @@ -174,6 +174,9 @@ class Deck(models.Model): # added for better DRF view return self.get_heroes() + def score(self): + return sum([x.attack + x.health + x.speed for x in self.get_heroes()]) + class Meta: db_table = "deck" verbose_name = "deck" diff --git a/room/admin.py b/room/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/room/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/room/consumers.py b/room/consumers.py index 87b627e..6fc561f 100644 --- a/room/consumers.py +++ b/room/consumers.py @@ -1,30 +1,110 @@ import json + +from asgiref.sync import sync_to_async from channels.generic.websocket import AsyncWebsocketConsumer +from game.models import Deck +from room.models import PlayerInQueue -class QueConsumer(AsyncWebsocketConsumer): - def __init__(self, *args, **kwargs): - super().__init__(args, kwargs) - self.room_group_name = None +class QueueConsumer(AsyncWebsocketConsumer): async def connect(self): - self.room_group_name = "que" - - await self.channel_layer.group_add(self.room_group_name, self.channel_name) + self.room_group_name = "queue" await self.accept() + await self.check_origin() + + await self.channel_layer.group_add(self.room_group_name, self.channel_name) async def disconnect(self, close_code): await self.channel_layer.group_discard(self.room_group_name, self.channel_name) # Receive message from WebSocket async def receive(self, text_data): - print(text_data) - # Send message to room group - await self.channel_layer.group_send( - self.room_group_name, - {"type": "chat_message", "message": text_data}, - ) + data = None + + try: + data = json.loads(text_data) + except ValueError: + await self.send( + text_data=json.dumps( + {"type": "ERROR", "message": "data is not JSON serializable"} + ) + ) + if data: + # TODO move to external function/class + if "type" not in data: + await self.send( + text_data=json.dumps( + {"type": "ERROR", "message": "incorrect data typing"} + ) + ) + else: + if data["type"] == "connect": + if "deck_id" not in data: + await self.send( + text_data=json.dumps( + {"type": "ERROR", "message": "deck id is not provided"} + ) + ) + else: + deck = None + # validate deck and check user originality + try: + deck_id = int(data["deck_id"]) + deck = await self.check_user_deck(deck_id) + except ValueError: + await self.send( + text_data=json.dumps( + {"type": "ERROR", "message": "deck id is incorrect"} + ) + ) + if deck: + await self.queue_connector(deck) + else: + await self.send( + text_data=json.dumps( + { + "type": "ERROR", + "message": "such deck doesn't exist", + } + ) + ) + + @sync_to_async + def check_user_deck(self, deck_id: int): + try: + deck = Deck.objects.get(id=deck_id) + if deck.player.id != self.scope["player"]: + return False + return deck + except Deck.DoesNotExist: + return False + + @sync_to_async + def queue_connector(self, deck): + try: + queue = PlayerInQueue.objects.get( + player_id=self.scope["player"] + ).score = deck.score() + except PlayerInQueue.DoesNotExist: + queue = PlayerInQueue.objects.create( + player_id=self.scope["player"], score=deck.score() + ) + + self.scope["queue"] = queue + + async def chat_message(self, event): + pass + + async def check_origin(self): + if not self.scope["player"]: + await self.send( + text_data=json.dumps( + {"type": "ERROR", "message": "token is incorrect or expired"} + ) + ) + await self.close() class RoomConsumer(AsyncWebsocketConsumer): diff --git a/room/middleware.py b/room/middleware.py index e93e122..4f3d828 100644 --- a/room/middleware.py +++ b/room/middleware.py @@ -6,17 +6,21 @@ from game.services.jwt import read_jwt @database_sync_to_async -def get_player(jwt): +def get_player(headers): + # WARNING headers type is bytes + if b"authorization" not in headers or not headers[b"authorization"]: + return False + + jwt = headers[b"authorization"].decode() payload = read_jwt(jwt) - if not payload: - raise PermissionDenied - try: - return Player.objects.get(id=payload) - except User.DoesNotExist: - return AnonymousUser() + + if not payload or "id" not in payload: + return False + + return payload["id"] -class QueryAuthMiddleware: +class HeaderAuthMiddleware: """Custom middleware to read user auth token from string.""" def __init__(self, app): @@ -24,9 +28,6 @@ class QueryAuthMiddleware: self.app = app async def __call__(self, scope, receive, send): - # Look up user from query string (you should also do things like - # checking if it is a valid user ID, or if scope["user"] is already - # populated). - scope["user"] = await get_user(int(scope["query_string"])) + scope["player"] = await get_player(dict(scope["headers"])) return await self.app(scope, receive, send) diff --git a/room/models.py b/room/models.py index 35940fc..d5bd086 100644 --- a/room/models.py +++ b/room/models.py @@ -4,7 +4,7 @@ from django.db import models from game.models import Player -class PlayerInQue(models.Model): +class PlayerInQueue(models.Model): # TODO use redis for storing player = models.ForeignKey(Player, unique=True, on_delete=models.CASCADE) score = models.IntegerField() diff --git a/room/routing.py b/room/routing.py index c74db1e..9a299c0 100644 --- a/room/routing.py +++ b/room/routing.py @@ -3,6 +3,6 @@ from django.urls import path from . import consumers websocket_urlpatterns = [ - path("room", consumers.QueConsumer.as_asgi()), + path("room/", consumers.QueueConsumer.as_asgi()), path("room//", consumers.RoomConsumer.as_asgi()), ]