mirror of
https://github.com/evgen-app/chess_rpg_backend.git
synced 2024-11-25 19:14:02 +03:00
Removed files for stable branch
This commit is contained in:
parent
a3aafd19b2
commit
ba0c8499e0
133
.gitignore
vendored
133
.gitignore
vendored
|
@ -1,133 +0,0 @@
|
||||||
.idea
|
|
||||||
static/
|
|
||||||
media/
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
pip-wheel-metadata/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
.python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
37
README.md
37
README.md
|
@ -1,37 +0,0 @@
|
||||||
# chess_rpg_backend
|
|
||||||
Backend for chess rpg game
|
|
||||||
|
|
||||||
### installation
|
|
||||||
```shell
|
|
||||||
$ python3 manage.py makemigrations & python3 manage.py migrate
|
|
||||||
$ docker run -p 6379:6379 -d redis:5
|
|
||||||
```
|
|
||||||
|
|
||||||
### run
|
|
||||||
```shell
|
|
||||||
$ python3 manage.py runserver 0.0.0.0:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Описание команд сокетов
|
|
||||||
```python
|
|
||||||
# подключиние к очереди(ws://room/)
|
|
||||||
{
|
|
||||||
"type": "connect",
|
|
||||||
"deck_id": int
|
|
||||||
}
|
|
||||||
|
|
||||||
# коннект к комнате (сообщение от сервера)
|
|
||||||
{
|
|
||||||
"type": "INFO",
|
|
||||||
"opponent_score": int,
|
|
||||||
"coordinates" : [(x: int, y: int, type: str, model_url: url, your: bool), ...],
|
|
||||||
"opponent_online": true,
|
|
||||||
"first": bool
|
|
||||||
}
|
|
||||||
|
|
||||||
# состояние оппонента в комнате(сообщение от сервера)
|
|
||||||
{
|
|
||||||
"type": "INFO",
|
|
||||||
"message": "opponent is online" / "opponent is offline"
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,18 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
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": HeaderAuthMiddleware(
|
|
||||||
URLRouter(room.routing.websocket_urlpatterns)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
|
@ -1,104 +0,0 @@
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = "django-insecure-%_8sy196w4hzo9^cp9(@r=i+amh47r4mxfhq_(ok&=c(@%bhmk"
|
|
||||||
TOKEN_EXP = 2678400 # 31 day
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
"django.contrib.sessions",
|
|
||||||
"django.contrib.auth",
|
|
||||||
"django.contrib.contenttypes",
|
|
||||||
"django.contrib.messages",
|
|
||||||
# Packages
|
|
||||||
"rest_framework",
|
|
||||||
"channels",
|
|
||||||
# Apps
|
|
||||||
"game",
|
|
||||||
"room",
|
|
||||||
]
|
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
INSTALLED_APPS.append("django.contrib.staticfiles")
|
|
||||||
INSTALLED_APPS.append("drf_yasg")
|
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
"django.middleware.security.SecurityMiddleware",
|
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
||||||
"django.middleware.common.CommonMiddleware",
|
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = "chess_backend.urls"
|
|
||||||
|
|
||||||
ASGI_APPLICATION = "chess_backend.asgi.application"
|
|
||||||
CHANNEL_LAYERS = {
|
|
||||||
"default": {
|
|
||||||
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
|
||||||
"CONFIG": {
|
|
||||||
"hosts": [("127.0.0.1", 6379)],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
WSGI_APPLICATION = "chess_backend.wsgi.application"
|
|
||||||
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
"default": {
|
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
|
||||||
"NAME": BASE_DIR / "db.sqlite3",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LANGUAGES = [
|
|
||||||
("en-us", "English"),
|
|
||||||
("ru", "Russian"),
|
|
||||||
]
|
|
||||||
|
|
||||||
TIME_ZONE = "Europe/Moscow"
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
MEDIA_URL = "/media/"
|
|
||||||
STATIC_URL = "/static/"
|
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
|
|
||||||
else:
|
|
||||||
MEDIA_ROOT = "/var/www/media/"
|
|
||||||
STATIC_ROOT = "/var/www/static/"
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
||||||
|
|
||||||
LOGGING = {
|
|
||||||
"version": 1,
|
|
||||||
"disable_existing_loggers": False,
|
|
||||||
"handlers": {
|
|
||||||
"file": {
|
|
||||||
"level": "WARNING",
|
|
||||||
"class": "logging.FileHandler",
|
|
||||||
"filename": "debug.log",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"django": {
|
|
||||||
"handlers": ["file"],
|
|
||||||
"level": "DEBUG",
|
|
||||||
"propagate": True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
from django.conf.urls.static import static
|
|
||||||
from django.template.defaulttags import url
|
|
||||||
from django.urls import path, include, re_path
|
|
||||||
|
|
||||||
# openapi schema
|
|
||||||
from rest_framework import permissions
|
|
||||||
from drf_yasg.views import get_schema_view
|
|
||||||
from drf_yasg import openapi
|
|
||||||
|
|
||||||
|
|
||||||
schema_view = get_schema_view(
|
|
||||||
openapi.Info(
|
|
||||||
title="Snippets API",
|
|
||||||
default_version="v1",
|
|
||||||
description="Test description",
|
|
||||||
terms_of_service="https://www.google.com/policies/terms/",
|
|
||||||
contact=openapi.Contact(email="contact@snippets.local"),
|
|
||||||
license=openapi.License(name="BSD License"),
|
|
||||||
),
|
|
||||||
public=True,
|
|
||||||
permission_classes=(permissions.AllowAny,),
|
|
||||||
)
|
|
||||||
|
|
||||||
urlpatterns = [path("api/", include("game.urls"))] + static(
|
|
||||||
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
|
|
||||||
)
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
|
||||||
urlpatterns += [
|
|
||||||
re_path(
|
|
||||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
|
||||||
schema_view.without_ui(cache_timeout=0),
|
|
||||||
name="schema-json",
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
r"^swagger/$",
|
|
||||||
schema_view.with_ui("swagger", cache_timeout=0),
|
|
||||||
name="schema-swagger-ui",
|
|
||||||
),
|
|
||||||
re_path(
|
|
||||||
r"^redoc/$",
|
|
||||||
schema_view.with_ui("redoc", cache_timeout=0),
|
|
||||||
name="schema-redoc",
|
|
||||||
),
|
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
|
|
@ -1,16 +0,0 @@
|
||||||
"""
|
|
||||||
WSGI config for chess_backend project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chess_backend.settings')
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
|
@ -1,14 +0,0 @@
|
||||||
import string
|
|
||||||
import secrets
|
|
||||||
|
|
||||||
from random import randint
|
|
||||||
|
|
||||||
|
|
||||||
def generate_charset(length: int):
|
|
||||||
return "".join(
|
|
||||||
secrets.choice(string.digits + string.ascii_letters) for _ in range(length)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def gen_ton():
|
|
||||||
return int("".join([str(randint(0, 9)) for _ in range(48)]))
|
|
|
@ -1,22 +0,0 @@
|
||||||
from rest_framework.test import APITestCase
|
|
||||||
from django.urls import reverse
|
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
|
|
||||||
class GetBasicTest(APITestCase):
|
|
||||||
def __init__(self, url):
|
|
||||||
super().__init__()
|
|
||||||
self._url = reverse(url)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self):
|
|
||||||
return self._url
|
|
||||||
|
|
||||||
@url.setter
|
|
||||||
def url(self, value):
|
|
||||||
self._url = reverse(value)
|
|
||||||
|
|
||||||
def test_accessibility(self):
|
|
||||||
"""Test if POST request is possible"""
|
|
||||||
response = self.client.post(self._url)
|
|
||||||
self.assertNotEqual(response, status.HTTP_404_NOT_FOUND)
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
from game.models import HeroImageSet
|
|
||||||
|
|
||||||
admin.site.register(HeroImageSet)
|
|
|
@ -1,139 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
from rest_framework.exceptions import ValidationError
|
|
||||||
|
|
||||||
from game.models import Hero, Player, HeroInDeck, Deck, PlayerAuthSession
|
|
||||||
from game.services.jwt import read_jwt
|
|
||||||
|
|
||||||
|
|
||||||
class CreateHeroSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Hero
|
|
||||||
fields = (
|
|
||||||
"type",
|
|
||||||
"health",
|
|
||||||
"attack",
|
|
||||||
"speed",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GetHeroSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Hero
|
|
||||||
fields = (
|
|
||||||
"added",
|
|
||||||
"type",
|
|
||||||
"idle_img",
|
|
||||||
"attack_img",
|
|
||||||
"die_img",
|
|
||||||
"health",
|
|
||||||
"attack",
|
|
||||||
"speed",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ListHeroSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Hero
|
|
||||||
fields = (
|
|
||||||
"uuid",
|
|
||||||
"type",
|
|
||||||
"idle_img",
|
|
||||||
"attack_img",
|
|
||||||
"die_img",
|
|
||||||
"health",
|
|
||||||
"attack",
|
|
||||||
"speed",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CreatePlayerSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Player
|
|
||||||
fields = ("ton_wallet", "name")
|
|
||||||
|
|
||||||
|
|
||||||
class CreateDeckSerializer(serializers.ModelSerializer):
|
|
||||||
hero_ids = serializers.ListSerializer(
|
|
||||||
child=serializers.UUIDField(), min_length=16, max_length=16
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Deck
|
|
||||||
fields = ("hero_ids",)
|
|
||||||
|
|
||||||
def validate_hero_ids(self, value):
|
|
||||||
if len(set(value)) != 16:
|
|
||||||
raise ValidationError("Some of the uuids are not unique")
|
|
||||||
|
|
||||||
for x in value:
|
|
||||||
if not (hero := Hero.objects.filter(uuid=x)):
|
|
||||||
raise ValidationError(f"Hero with uuid {x} doesn't exist")
|
|
||||||
|
|
||||||
if hero.first().player.id != self.context["request"].user.id:
|
|
||||||
raise ValidationError(
|
|
||||||
f"Attempt to manipulate player with id {hero.first().player.id} hero"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.context["request"].method in ["POST"]:
|
|
||||||
if deck := HeroInDeck.objects.filter(hero=hero.first()):
|
|
||||||
raise ValidationError(
|
|
||||||
f"Hero with uuid {x} is already in deck with id {deck.first().deck.id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
deck = Deck.objects.create(player=self.context["request"].user)
|
|
||||||
for x in validated_data["hero_ids"]:
|
|
||||||
HeroInDeck.objects.create(hero_id=x, deck=deck)
|
|
||||||
return deck
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
for x in instance.get_heroes():
|
|
||||||
HeroInDeck.objects.get(hero=x).delete()
|
|
||||||
|
|
||||||
for x in validated_data["hero_ids"]:
|
|
||||||
HeroInDeck.objects.create(hero_id=x, deck=instance)
|
|
||||||
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class GetPlayerSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Player
|
|
||||||
fields = ("id", "name")
|
|
||||||
|
|
||||||
|
|
||||||
class GetDeckSerializer(serializers.ModelSerializer):
|
|
||||||
player = GetPlayerSerializer()
|
|
||||||
heroes = ListHeroSerializer(many=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Deck
|
|
||||||
fields = ("player", "heroes")
|
|
||||||
|
|
||||||
|
|
||||||
class ObtainTokenPairSerializer(serializers.Serializer):
|
|
||||||
refresh_token = serializers.CharField(max_length=300)
|
|
||||||
|
|
||||||
def __init__(self, instance=None, data=None, **kwargs):
|
|
||||||
super().__init__(instance, data, **kwargs)
|
|
||||||
self.player_id = None
|
|
||||||
|
|
||||||
def validate_refresh_token(self, value):
|
|
||||||
payload = read_jwt(value)
|
|
||||||
if not payload:
|
|
||||||
raise ValidationError("Token is incorrect or expired")
|
|
||||||
|
|
||||||
if "jit" not in payload:
|
|
||||||
raise ValidationError("Token is incorrect")
|
|
||||||
|
|
||||||
jit = payload["jit"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
session = PlayerAuthSession.objects.get(jit=jit)
|
|
||||||
except PlayerAuthSession.DoesNotExist:
|
|
||||||
return ValidationError("Incorrect user session")
|
|
||||||
|
|
||||||
self.player_id = session.player.id
|
|
||||||
return value
|
|
|
@ -1,169 +0,0 @@
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from rest_framework.generics import GenericAPIView, UpdateAPIView
|
|
||||||
from rest_framework.mixins import (
|
|
||||||
CreateModelMixin,
|
|
||||||
RetrieveModelMixin,
|
|
||||||
ListModelMixin,
|
|
||||||
DestroyModelMixin,
|
|
||||||
UpdateModelMixin,
|
|
||||||
)
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from game.authentication import PlayerAuthentication
|
|
||||||
from game.models import Hero, Deck
|
|
||||||
from game.api.v1.serializers import (
|
|
||||||
CreateHeroSerializer,
|
|
||||||
GetHeroSerializer,
|
|
||||||
CreatePlayerSerializer,
|
|
||||||
ListHeroSerializer,
|
|
||||||
CreateDeckSerializer,
|
|
||||||
GetDeckSerializer,
|
|
||||||
ObtainTokenPairSerializer,
|
|
||||||
)
|
|
||||||
from game.services.jwt import sign_jwt
|
|
||||||
|
|
||||||
|
|
||||||
class ListCreateHeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
|
|
||||||
authentication_classes = (PlayerAuthentication,)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
return serializer.save()
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
instance = self.perform_create(serializer)
|
|
||||||
return Response({"uuid": instance.uuid}, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return self.list(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_serializer_class(self):
|
|
||||||
if self.request.method == "GET":
|
|
||||||
return ListHeroSerializer
|
|
||||||
else:
|
|
||||||
return CreateHeroSerializer
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Hero.objects.filter(player_id=self.request.user.id)
|
|
||||||
|
|
||||||
|
|
||||||
class RetrieveHeroView(RetrieveModelMixin, UpdateAPIView, GenericAPIView):
|
|
||||||
serializer_class = GetHeroSerializer
|
|
||||||
lookup_field = "uuid"
|
|
||||||
queryset = Hero.objects.all()
|
|
||||||
|
|
||||||
def get_authenticators(self):
|
|
||||||
if self.request.method != "GET":
|
|
||||||
self.authentication_classes = [PlayerAuthentication]
|
|
||||||
return [auth() for auth in self.authentication_classes]
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return self.retrieve(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def put(self, request, *args, **kwargs):
|
|
||||||
return self.update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def patch(self, request, *args, **kwargs):
|
|
||||||
return self.partial_update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerCreateView(GenericAPIView, CreateModelMixin):
|
|
||||||
serializer_class = CreatePlayerSerializer
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
return serializer.save()
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
instance = self.perform_create(serializer)
|
|
||||||
|
|
||||||
access_jwt = instance.get_access_token()
|
|
||||||
refresh_jwt = instance.get_refresh_token()
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
"access_token": access_jwt,
|
|
||||||
"refresh_token": refresh_jwt,
|
|
||||||
"deck_id": instance.get_last_deck().id,
|
|
||||||
},
|
|
||||||
status=status.HTTP_201_CREATED,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DeckCreateView(GenericAPIView, CreateModelMixin):
|
|
||||||
serializer_class = CreateDeckSerializer
|
|
||||||
authentication_classes = (PlayerAuthentication,)
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
|
||||||
return serializer.save(player=self.request.user)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
instance = self.perform_create(serializer)
|
|
||||||
heroes_list = ListHeroSerializer(instance.get_heroes(), many=True)
|
|
||||||
heroes_list.data["deck_id"] = instance.id
|
|
||||||
return Response(heroes_list.data, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
|
|
||||||
class RetireUpdateDeleteDeckView(
|
|
||||||
RetrieveHeroView, DestroyModelMixin, UpdateModelMixin, GenericAPIView
|
|
||||||
):
|
|
||||||
lookup_field = "id"
|
|
||||||
queryset = Deck.objects.all()
|
|
||||||
|
|
||||||
def get_serializer_class(self):
|
|
||||||
if self.request.method == "GET":
|
|
||||||
return GetDeckSerializer
|
|
||||||
else:
|
|
||||||
return CreateDeckSerializer
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return self.retrieve(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
|
||||||
return serializer.update(self.get_object(), self.request.data)
|
|
||||||
|
|
||||||
def put(self, request, *args, **kwargs):
|
|
||||||
if not self._check_user_identity(kwargs["id"]):
|
|
||||||
return Response(
|
|
||||||
"Attempt to change another user's deck",
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
instance = self.perform_update(serializer)
|
|
||||||
heroes_list = ListHeroSerializer(instance.get_heroes(), many=True)
|
|
||||||
return Response(heroes_list.data, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
|
||||||
if not self._check_user_identity(kwargs["id"]):
|
|
||||||
return Response(
|
|
||||||
"Attempt to delete another user's deck",
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
self.destroy(request, *args, **kwargs)
|
|
||||||
return Response(
|
|
||||||
f"Destroyed deck with id {kwargs['id']}", status=status.HTTP_200_OK
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_user_identity(self, deck_id) -> bool:
|
|
||||||
return deck_id in list(
|
|
||||||
Deck.objects.filter(player_id=self.request.user.id).values_list(
|
|
||||||
"id", flat=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshAuthKey(GenericAPIView):
|
|
||||||
serializer_class = ObtainTokenPairSerializer
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
access_jwt = sign_jwt(
|
|
||||||
{"id": serializer.player_id, "type": "access"}, t_life=3600
|
|
||||||
)
|
|
||||||
return Response({"access_token": access_jwt}, status=status.HTTP_200_OK)
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class GameConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'game'
|
|
|
@ -1,35 +0,0 @@
|
||||||
from jwt import DecodeError
|
|
||||||
from rest_framework import authentication
|
|
||||||
from rest_framework import exceptions
|
|
||||||
from .models import Player
|
|
||||||
from .services.jwt import read_jwt
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerAuthentication(authentication.BaseAuthentication):
|
|
||||||
def authenticate(self, request):
|
|
||||||
|
|
||||||
if "Authorization" not in request.headers or not (
|
|
||||||
token := request.headers["Authorization"]
|
|
||||||
):
|
|
||||||
raise exceptions.AuthenticationFailed("No credentials provided.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
t = read_jwt(token)
|
|
||||||
except DecodeError:
|
|
||||||
raise exceptions.AuthenticationFailed("Token is incorrect")
|
|
||||||
|
|
||||||
if not t:
|
|
||||||
raise exceptions.AuthenticationFailed("Token is incorrect of expired")
|
|
||||||
|
|
||||||
if "id" not in t and "type" not in t:
|
|
||||||
raise exceptions.AuthenticationFailed("No user data")
|
|
||||||
|
|
||||||
if t["type"] != "access":
|
|
||||||
raise exceptions.AuthenticationFailed("Incorrect token type")
|
|
||||||
|
|
||||||
try:
|
|
||||||
user = Player.objects.get(id=int(t["id"]))
|
|
||||||
except Player.DoesNotExist:
|
|
||||||
raise exceptions.AuthenticationFailed("No such user")
|
|
||||||
|
|
||||||
return user, None
|
|
|
@ -1,57 +0,0 @@
|
||||||
# Generated by Django 4.0.5 on 2022-06-04 14:16
|
|
||||||
|
|
||||||
import django.core.validators
|
|
||||||
from django.db import migrations, models
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Hero',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
|
||||||
('added', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('type', models.CharField(choices=[('WIZARD', 'wizard'), ('ARCHER', 'archer'), ('WARRIOR', 'warrior')], max_length=7)),
|
|
||||||
('idle_img', models.ImageField(upload_to='uploads/idle')),
|
|
||||||
('attack_img', models.ImageField(upload_to='uploads/attack')),
|
|
||||||
('die_img', models.ImageField(upload_to='uploads/die')),
|
|
||||||
('health', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])),
|
|
||||||
('speed', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)])),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'hero',
|
|
||||||
'verbose_name_plural': 'heroes',
|
|
||||||
'ordering': ['-added'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Player',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('ton_wallet', models.CharField(max_length=50, verbose_name='TON wallet')),
|
|
||||||
('name', models.CharField(blank=True, max_length=100)),
|
|
||||||
('added', models.DateTimeField(auto_now_add=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'player',
|
|
||||||
'verbose_name_plural': 'players',
|
|
||||||
'ordering': ['-added'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='player',
|
|
||||||
index=models.Index(fields=['ton_wallet'], name='game_player_ton_wal_47dd93_idx'),
|
|
||||||
),
|
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='hero',
|
|
||||||
index=models.Index(fields=['uuid'], name='game_hero_uuid_ada5d9_idx'),
|
|
||||||
),
|
|
||||||
]
|
|
216
game/models.py
216
game/models.py
|
@ -1,216 +0,0 @@
|
||||||
import random
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.core.validators import (
|
|
||||||
MinValueValidator,
|
|
||||||
MaxValueValidator,
|
|
||||||
MinLengthValidator,
|
|
||||||
MaxLengthValidator,
|
|
||||||
)
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from common.generators import generate_charset
|
|
||||||
from game.services.jwt import sign_jwt
|
|
||||||
|
|
||||||
|
|
||||||
class HeroTypes(models.TextChoices):
|
|
||||||
wizard = "WIZARD", "wizard"
|
|
||||||
archer = "ARCHER", "archer"
|
|
||||||
warrior = "WARRIOR", "warrior"
|
|
||||||
king = "KING", "king"
|
|
||||||
|
|
||||||
|
|
||||||
class Player(models.Model):
|
|
||||||
"""base model to handle and store users"""
|
|
||||||
|
|
||||||
ton_wallet = models.CharField(
|
|
||||||
verbose_name="TON wallet",
|
|
||||||
validators=[MinLengthValidator(48), MaxLengthValidator(48)],
|
|
||||||
max_length=48,
|
|
||||||
unique=True,
|
|
||||||
)
|
|
||||||
name = models.CharField(max_length=100, blank=True)
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def save(
|
|
||||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
|
||||||
):
|
|
||||||
"""saves user and creates deck for him with 16 heroes"""
|
|
||||||
super(Player, self).save()
|
|
||||||
PlayerAuthSession.objects.create(player=self)
|
|
||||||
deck = Deck.objects.create(player=self)
|
|
||||||
types = (
|
|
||||||
["KING"]
|
|
||||||
+ ["ARCHER" for _ in range(4)]
|
|
||||||
+ ["WARRIOR" for _ in range(6)]
|
|
||||||
+ ["WIZARD" for _ in range(2)]
|
|
||||||
+ [random.choice(HeroTypes.choices[:3])[0] for _ in range(3)]
|
|
||||||
)
|
|
||||||
for t in types:
|
|
||||||
hero = Hero()
|
|
||||||
hero.player = self
|
|
||||||
hero.type = t
|
|
||||||
|
|
||||||
hero.health = random.randint(0, 10)
|
|
||||||
hero.attack = random.randint(0, 10)
|
|
||||||
hero.speed = random.randint(0, 10)
|
|
||||||
|
|
||||||
hero.save()
|
|
||||||
HeroInDeck.objects.create(deck=deck, hero=hero)
|
|
||||||
|
|
||||||
def get_last_deck(self):
|
|
||||||
return Deck.objects.filter(player=self).last()
|
|
||||||
|
|
||||||
def get_auth_session(self):
|
|
||||||
return PlayerAuthSession.objects.get(player=self).jit
|
|
||||||
|
|
||||||
def get_refresh_token(self):
|
|
||||||
return sign_jwt({"jit": self.get_auth_session(), "type": "refresh"})
|
|
||||||
|
|
||||||
def get_access_token(self):
|
|
||||||
return sign_jwt({"id": self.id, "type": "access"}, t_life=3600)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [models.Index(fields=["ton_wallet"])]
|
|
||||||
ordering = ["-created"]
|
|
||||||
|
|
||||||
db_table = "player"
|
|
||||||
verbose_name = "player"
|
|
||||||
verbose_name_plural = "players"
|
|
||||||
|
|
||||||
|
|
||||||
class Hero(models.Model):
|
|
||||||
"""Model to store heroes and their stats, connected to player"""
|
|
||||||
|
|
||||||
uuid = models.UUIDField(
|
|
||||||
default=uuid.uuid4, editable=False, unique=True, primary_key=True
|
|
||||||
)
|
|
||||||
player = models.ForeignKey(
|
|
||||||
Player,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="heroes",
|
|
||||||
related_query_name="hero",
|
|
||||||
)
|
|
||||||
added = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
type = models.CharField(blank=False, choices=HeroTypes.choices, max_length=7)
|
|
||||||
model = models.ForeignKey("HeroModelSet", on_delete=models.CASCADE)
|
|
||||||
health = models.IntegerField(
|
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
|
||||||
)
|
|
||||||
attack = models.IntegerField(
|
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
|
||||||
)
|
|
||||||
speed = models.IntegerField(
|
|
||||||
validators=[MinValueValidator(1), MaxValueValidator(10)], blank=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def idle_img(self):
|
|
||||||
return self.idle_img_f.image.url
|
|
||||||
|
|
||||||
def attack_img(self):
|
|
||||||
return self.attack_img_f.image.url
|
|
||||||
|
|
||||||
def die_img(self):
|
|
||||||
return self.die_img_f.image.url
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.type} {self.player.name}"
|
|
||||||
|
|
||||||
def save(
|
|
||||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
|
||||||
):
|
|
||||||
self.idle_img_f = random.choice(
|
|
||||||
[x for x in HeroModelSet.objects.filter(hero_type=self.type)]
|
|
||||||
)
|
|
||||||
self.attack_img_f = random.choice(
|
|
||||||
[x for x in HeroModelSet.objects.filter(hero_type=self.type)]
|
|
||||||
)
|
|
||||||
self.die_img_f = random.choice(
|
|
||||||
[x for x in HeroModelSet.objects.filter(hero_type=self.type)]
|
|
||||||
)
|
|
||||||
super(Hero, self).save()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [models.Index(fields=["uuid"])]
|
|
||||||
ordering = ["-added"]
|
|
||||||
|
|
||||||
db_table = "hero"
|
|
||||||
verbose_name = "hero"
|
|
||||||
verbose_name_plural = "heroes"
|
|
||||||
|
|
||||||
|
|
||||||
class HeroModelSet(models.Model):
|
|
||||||
hero_type = models.CharField(blank=False, choices=HeroTypes.choices, max_length=7)
|
|
||||||
model = models.ImageField(upload_to="uploads/")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.hero_type} model file"
|
|
||||||
|
|
||||||
|
|
||||||
class Deck(models.Model):
|
|
||||||
player = models.ForeignKey(
|
|
||||||
Player,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="decks",
|
|
||||||
related_query_name="deck",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.player.name}'s deck"
|
|
||||||
|
|
||||||
def get_heroes(self):
|
|
||||||
return [x.hero for x in HeroInDeck.objects.filter(deck=self)]
|
|
||||||
|
|
||||||
def heroes(self):
|
|
||||||
# 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"
|
|
||||||
verbose_name_plural = "decks"
|
|
||||||
|
|
||||||
|
|
||||||
class HeroInDeck(models.Model):
|
|
||||||
deck = models.ForeignKey(
|
|
||||||
Deck,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="hero_in_deck",
|
|
||||||
related_query_name="heroes",
|
|
||||||
)
|
|
||||||
hero = models.OneToOneField(
|
|
||||||
Hero,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="hero_in_deck",
|
|
||||||
related_query_name="decks",
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = "hero_in_deck"
|
|
||||||
verbose_name = "Hero in deck"
|
|
||||||
verbose_name_plural = "Heroes in decks"
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerAuthSession(models.Model):
|
|
||||||
player = models.OneToOneField(
|
|
||||||
Player, unique_for_month=True, on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
jit = models.CharField(max_length=30)
|
|
||||||
|
|
||||||
def save(
|
|
||||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
|
||||||
):
|
|
||||||
self.jit = generate_charset(30)
|
|
||||||
super(PlayerAuthSession, self).save(
|
|
||||||
force_insert=force_insert,
|
|
||||||
force_update=force_update,
|
|
||||||
using=using,
|
|
||||||
update_fields=update_fields,
|
|
||||||
)
|
|
|
@ -1,45 +0,0 @@
|
||||||
import jwt
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from django.conf import settings
|
|
||||||
from jwt import ExpiredSignatureError, InvalidSignatureError
|
|
||||||
|
|
||||||
TIMEZONE = pytz.timezone("Europe/Moscow")
|
|
||||||
|
|
||||||
|
|
||||||
def sign_jwt(data: dict, t_life: None | int = None) -> str:
|
|
||||||
"""generate and sign jwt with iat and exp using data from settings"""
|
|
||||||
iat = int(datetime.now(tz=TIMEZONE).timestamp())
|
|
||||||
exp = iat + settings.TOKEN_EXP if not t_life else iat + t_life
|
|
||||||
payload = {"iat": iat, "exp": exp}
|
|
||||||
for nm, el in data.items():
|
|
||||||
if nm not in ["iat", "exp"]:
|
|
||||||
payload[nm] = el
|
|
||||||
|
|
||||||
secret = settings.SECRET_KEY
|
|
||||||
token = jwt.encode(payload=payload, key=secret)
|
|
||||||
return token
|
|
||||||
|
|
||||||
|
|
||||||
def read_jwt(token: str) -> dict | bool:
|
|
||||||
"""reads jwt, validates it and return payload if correct"""
|
|
||||||
header_data = jwt.get_unverified_header(token)
|
|
||||||
secret = settings.SECRET_KEY
|
|
||||||
try:
|
|
||||||
payload = jwt.decode(token, key=secret, algorithms=[header_data["alg"]])
|
|
||||||
except ExpiredSignatureError as e:
|
|
||||||
return False
|
|
||||||
except InvalidSignatureError as e:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if "exp" not in payload:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if int(datetime.now(tz=TIMEZONE).timestamp()) > payload["exp"]:
|
|
||||||
return False
|
|
||||||
|
|
||||||
payload.pop("iat", None)
|
|
||||||
payload.pop("exp", None)
|
|
||||||
|
|
||||||
return payload
|
|
21
game/urls.py
21
game/urls.py
|
@ -1,21 +0,0 @@
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from game.api.v1.views import (
|
|
||||||
ListCreateHeroView,
|
|
||||||
RetrieveHeroView,
|
|
||||||
PlayerCreateView,
|
|
||||||
DeckCreateView,
|
|
||||||
RetireUpdateDeleteDeckView,
|
|
||||||
RefreshAuthKey,
|
|
||||||
)
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("v1/hero/", ListCreateHeroView.as_view(), name="hero_api_create"),
|
|
||||||
path("v1/hero/<uuid:uuid>", RetrieveHeroView.as_view(), name="hero_api_retrieve"),
|
|
||||||
path("v1/player/refresh", RefreshAuthKey.as_view(), name="player_create_api"),
|
|
||||||
path("v1/player/", PlayerCreateView.as_view(), name="player_create_api"),
|
|
||||||
path("v1/deck/", DeckCreateView.as_view(), name="deck_create_api"),
|
|
||||||
path(
|
|
||||||
"v1/deck/<int:id>", RetireUpdateDeleteDeckView.as_view(), name="deck_retire_api"
|
|
||||||
),
|
|
||||||
]
|
|
22
manage.py
22
manage.py
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
"""Django's command-line utility for administrative tasks."""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run administrative tasks."""
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chess_backend.settings')
|
|
||||||
try:
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
except ImportError as exc:
|
|
||||||
raise ImportError(
|
|
||||||
"Couldn't import Django. Are you sure it's installed and "
|
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
|
||||||
"forget to activate a virtual environment?"
|
|
||||||
) from exc
|
|
||||||
execute_from_command_line(sys.argv)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,46 +0,0 @@
|
||||||
asgiref==3.5.2
|
|
||||||
asttokens==2.0.5
|
|
||||||
attrs==21.4.0
|
|
||||||
autobahn==22.5.1
|
|
||||||
Automat==20.2.0
|
|
||||||
backcall==0.2.0
|
|
||||||
cffi==1.15.0
|
|
||||||
channels==3.0.4
|
|
||||||
constantly==15.1.0
|
|
||||||
cryptography==37.0.2
|
|
||||||
daphne==3.0.2
|
|
||||||
decorator==5.1.1
|
|
||||||
Django==4.0.5
|
|
||||||
djangorestframework==3.13.1
|
|
||||||
executing==0.8.3
|
|
||||||
hyperlink==21.0.0
|
|
||||||
idna==3.3
|
|
||||||
incremental==21.3.0
|
|
||||||
ipython==8.4.0
|
|
||||||
jedi==0.18.1
|
|
||||||
matplotlib-inline==0.1.3
|
|
||||||
parso==0.8.3
|
|
||||||
pexpect==4.8.0
|
|
||||||
pickleshare==0.7.5
|
|
||||||
Pillow==9.1.1
|
|
||||||
prompt-toolkit==3.0.29
|
|
||||||
ptyprocess==0.7.0
|
|
||||||
pure-eval==0.2.2
|
|
||||||
pyasn1==0.4.8
|
|
||||||
pyasn1-modules==0.2.8
|
|
||||||
pycparser==2.21
|
|
||||||
Pygments==2.12.0
|
|
||||||
PyJWT==2.4.0
|
|
||||||
pyOpenSSL==22.0.0
|
|
||||||
pytz==2022.1
|
|
||||||
service-identity==21.1.0
|
|
||||||
six==1.16.0
|
|
||||||
sqlparse==0.4.2
|
|
||||||
stack-data==0.2.0
|
|
||||||
traitlets==5.2.2.post1
|
|
||||||
Twisted==22.4.0
|
|
||||||
txaio==22.2.1
|
|
||||||
typing_extensions==4.2.0
|
|
||||||
wcwidth==0.2.5
|
|
||||||
zope.interface==5.4.0
|
|
||||||
channels_redis
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class RoomConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'room'
|
|
|
@ -1,397 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
|
||||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
|
||||||
from channels.layers import get_channel_layer
|
|
||||||
|
|
||||||
from game.models import Deck
|
|
||||||
from room.models import PlayerInQueue, Room, PlayerInRoom, GameState
|
|
||||||
from room.services.room_create import create_room
|
|
||||||
|
|
||||||
channel_layer = get_channel_layer()
|
|
||||||
|
|
||||||
|
|
||||||
class QueueConsumer(AsyncWebsocketConsumer):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(args, kwargs)
|
|
||||||
self.room_group_name = None
|
|
||||||
|
|
||||||
async def connect(self):
|
|
||||||
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.delete_user_in_queue()
|
|
||||||
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
|
|
||||||
|
|
||||||
# Receive message from WebSocket
|
|
||||||
async def receive(self, 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:
|
|
||||||
# add to que, start finding players
|
|
||||||
await self.queue_connector(deck)
|
|
||||||
await self.send(
|
|
||||||
text_data=json.dumps(
|
|
||||||
{
|
|
||||||
"type": "INFO",
|
|
||||||
"message": f"added to queue deck with score {self.scope['score']}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
opponent = await self.find_user_by_score()
|
|
||||||
|
|
||||||
if not opponent:
|
|
||||||
await self.send(
|
|
||||||
text_data=json.dumps(
|
|
||||||
{
|
|
||||||
"type": "INFO",
|
|
||||||
"message": "no user found, awaiting in queue",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# add to group and send message that opponent found to players
|
|
||||||
room = await create_room(
|
|
||||||
deck_id_1=self.scope["deck"],
|
|
||||||
player_id_1=self.scope["player"],
|
|
||||||
player_score_1=self.scope["score"],
|
|
||||||
deck_id_2=opponent[2],
|
|
||||||
player_id_2=opponent[3],
|
|
||||||
player_score_2=opponent[1],
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.channel_layer.send(
|
|
||||||
opponent[0],
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"message": f"user found, with score {self.scope['score']}",
|
|
||||||
"room": room,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.send(
|
|
||||||
text_data=json.dumps(
|
|
||||||
{
|
|
||||||
"type": "INFO",
|
|
||||||
"message": f"user found, with score {opponent[1]}",
|
|
||||||
"room": room,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await self.send(
|
|
||||||
text_data=json.dumps(
|
|
||||||
{
|
|
||||||
"type": "ERROR",
|
|
||||||
"message": "such deck doesn't exist",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@sync_to_async
|
|
||||||
def delete_user_in_queue(self):
|
|
||||||
try:
|
|
||||||
PlayerInQueue.objects.get(player_id=self.scope["player"]).delete()
|
|
||||||
except PlayerInQueue.DoesNotExist:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@sync_to_async
|
|
||||||
def find_user_by_score(self):
|
|
||||||
s_min = self.scope["score"] * 0.95
|
|
||||||
s_max = self.scope["score"] * 1.05
|
|
||||||
for el in PlayerInQueue.objects.all():
|
|
||||||
if el.player_id != self.scope["player"]:
|
|
||||||
if s_min <= el.score <= s_max:
|
|
||||||
return el.channel_name, el.score, el.deck.id, el.player.id
|
|
||||||
return False
|
|
||||||
|
|
||||||
@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"])
|
|
||||||
queue.score = deck.score()
|
|
||||||
queue.channel_name = self.channel_name
|
|
||||||
queue.save()
|
|
||||||
|
|
||||||
except PlayerInQueue.DoesNotExist:
|
|
||||||
queue = PlayerInQueue.objects.create(
|
|
||||||
player_id=self.scope["player"],
|
|
||||||
deck=deck,
|
|
||||||
score=deck.score(),
|
|
||||||
channel_name=self.channel_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.scope["queue"] = queue.id
|
|
||||||
self.scope["deck"] = deck.id
|
|
||||||
self.scope["score"] = queue.score
|
|
||||||
|
|
||||||
async def info(self, event):
|
|
||||||
message = event["message"]
|
|
||||||
msg = {"type": "INFO", "message": message}
|
|
||||||
if "room" in event:
|
|
||||||
msg["room"] = event["room"]
|
|
||||||
|
|
||||||
await self.send(text_data=json.dumps(msg))
|
|
||||||
|
|
||||||
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):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.room_group_name = None
|
|
||||||
self.room_name = None
|
|
||||||
|
|
||||||
async def connect(self):
|
|
||||||
await self.accept()
|
|
||||||
await self.check_origin()
|
|
||||||
|
|
||||||
if not await self.connect_to_room():
|
|
||||||
await self.close()
|
|
||||||
else:
|
|
||||||
message, round = await self.get_state()
|
|
||||||
|
|
||||||
await self.send(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"type": "INFO",
|
|
||||||
"opponent_score": self.scope["opponent_score"],
|
|
||||||
"opponent_deck": self.scope["opponent_deck"],
|
|
||||||
"opponent_online": self.scope["opponent_online"],
|
|
||||||
"first": self.scope["first"],
|
|
||||||
"state": message,
|
|
||||||
"round": round,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if "opponent_channel" in self.scope and self.scope["opponent_channel"]:
|
|
||||||
await self.channel_layer.send(
|
|
||||||
self.scope["opponent_channel"],
|
|
||||||
{
|
|
||||||
"type": "channel",
|
|
||||||
"channel": self.channel_name,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await self.channel_layer.send(
|
|
||||||
self.scope["opponent_channel"],
|
|
||||||
{"type": "connection_info", "online": True},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Join room group
|
|
||||||
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
|
|
||||||
|
|
||||||
@sync_to_async
|
|
||||||
def get_state(self):
|
|
||||||
state = self.scope["player_in_room"].get_state()
|
|
||||||
return state.message, state.round
|
|
||||||
|
|
||||||
@sync_to_async
|
|
||||||
def connect_to_room(self):
|
|
||||||
slug = self.scope["url_route"]["kwargs"]["room_name"]
|
|
||||||
|
|
||||||
self.room_name = slug
|
|
||||||
self.room_group_name = f"room_{slug}"
|
|
||||||
room = Room.objects.filter(slug=slug)
|
|
||||||
|
|
||||||
if not room:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.scope["room"] = room
|
|
||||||
|
|
||||||
# check if player can be in a room
|
|
||||||
p_ids = [x.player.id for x in room.first().players.all()]
|
|
||||||
if self.scope["player"] not in p_ids:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# add player info to scope
|
|
||||||
player = PlayerInRoom.objects.get(player_id=self.scope["player"])
|
|
||||||
|
|
||||||
self.scope["player_in_room"] = player
|
|
||||||
self.scope["first"] = player.first
|
|
||||||
self.scope["score"] = player.score
|
|
||||||
self.scope["deck"] = player.deck.id
|
|
||||||
|
|
||||||
p_ids.remove(player.player.id)
|
|
||||||
opponent = PlayerInRoom.objects.get(player_id=p_ids[0])
|
|
||||||
|
|
||||||
self.scope["opponent"] = opponent.player.id
|
|
||||||
self.scope["opponent_channel"] = opponent.channel_name
|
|
||||||
self.scope["opponent_score"] = opponent.score
|
|
||||||
self.scope["opponent_deck"] = opponent.deck.id
|
|
||||||
self.scope["opponent_first"] = opponent.first
|
|
||||||
self.scope["opponent_online"] = opponent.online
|
|
||||||
|
|
||||||
player.online = True
|
|
||||||
player.channel_name = self.channel_name
|
|
||||||
player.save(update_fields=["online", "channel_name"])
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def disconnect(self, close_code):
|
|
||||||
# Leave room group
|
|
||||||
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
|
|
||||||
await self.disconnect_player()
|
|
||||||
|
|
||||||
if "opponent_channel" in self.scope and self.scope["opponent_channel"]:
|
|
||||||
await self.channel_layer.send(
|
|
||||||
self.scope["opponent_channel"],
|
|
||||||
{"type": "connection_info", "online": False},
|
|
||||||
)
|
|
||||||
|
|
||||||
@sync_to_async
|
|
||||||
def disconnect_player(self):
|
|
||||||
if "player_in_room" in self.scope:
|
|
||||||
self.scope["player_in_room"].online = False
|
|
||||||
self.scope["player_in_room"].channel_name = None
|
|
||||||
self.scope["player_in_room"].save(update_fields=["online", "channel_name"])
|
|
||||||
|
|
||||||
# Receive message from WebSocket
|
|
||||||
async def receive(self, 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:
|
|
||||||
if data["type"] == "start":
|
|
||||||
if not await self.start(data):
|
|
||||||
await self.send(
|
|
||||||
text_data=json.dumps(
|
|
||||||
{"type": "ERROR", "message": "opponent is offline"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def start(self, data):
|
|
||||||
if self.scope["opponent_channel"] and self.scope["opponent_online"]:
|
|
||||||
await self.channel_layer.send(
|
|
||||||
self.scope["opponent_channel"],
|
|
||||||
{
|
|
||||||
"type": "info",
|
|
||||||
"message": "opponent is ready to start",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
# info type group message handler
|
|
||||||
async def info(self, event):
|
|
||||||
message = event["message"]
|
|
||||||
msg = {"type": "INFO", "message": message}
|
|
||||||
|
|
||||||
if "opponent_score" in event:
|
|
||||||
msg["opponent_score"] = event["opponent_score"]
|
|
||||||
|
|
||||||
if "opponent_deck" in event:
|
|
||||||
msg["opponent_deck"] = event["opponent_deck"]
|
|
||||||
|
|
||||||
if "opponent_online" in event:
|
|
||||||
msg["opponent_online"] = event["opponent_online"]
|
|
||||||
|
|
||||||
if "first" in event:
|
|
||||||
msg["first"] = event["first"]
|
|
||||||
|
|
||||||
if "state" in event:
|
|
||||||
msg["state"] = event["state"]
|
|
||||||
|
|
||||||
if "round" in event:
|
|
||||||
msg["round"] = event["round"]
|
|
||||||
|
|
||||||
await self.send(text_data=json.dumps(msg))
|
|
||||||
|
|
||||||
# Receive message from room group
|
|
||||||
async def chat_message(self, event):
|
|
||||||
message = event["message"]
|
|
||||||
|
|
||||||
# Send message to WebSocket
|
|
||||||
await self.send(text_data=json.dumps({"lot": message}))
|
|
||||||
|
|
||||||
async def channel(self, event):
|
|
||||||
channel = event["channel"]
|
|
||||||
self.scope["opponent_channel"] = channel
|
|
||||||
|
|
||||||
async def connection_info(self, event):
|
|
||||||
status = event["online"]
|
|
||||||
await self.send(
|
|
||||||
text_data=json.dumps(
|
|
||||||
{
|
|
||||||
"type": "INFO",
|
|
||||||
"message": "opponent is online"
|
|
||||||
if status
|
|
||||||
else "opponent is offline",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.scope["opponent_online"] = status
|
|
||||||
|
|
||||||
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()
|
|
|
@ -1,33 +0,0 @@
|
||||||
from channels.db import database_sync_to_async
|
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
|
|
||||||
from game.models import Player
|
|
||||||
from game.services.jwt import read_jwt
|
|
||||||
|
|
||||||
|
|
||||||
@database_sync_to_async
|
|
||||||
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 or "id" not in payload:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return payload["id"]
|
|
||||||
|
|
||||||
|
|
||||||
class HeaderAuthMiddleware:
|
|
||||||
"""Custom middleware to read user auth token from string."""
|
|
||||||
|
|
||||||
def __init__(self, app):
|
|
||||||
# Store the ASGI application we were passed
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
async def __call__(self, scope, receive, send):
|
|
||||||
scope["player"] = await get_player(dict(scope["headers"]))
|
|
||||||
|
|
||||||
return await self.app(scope, receive, send)
|
|
|
@ -1,15 +0,0 @@
|
||||||
# Generated by Django 4.0.5 on 2022-06-23 22:56
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
]
|
|
|
@ -1,50 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
from game.models import Player, Deck
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerInQueue(models.Model):
|
|
||||||
# TODO use redis for storing
|
|
||||||
player = models.OneToOneField(Player, unique=True, on_delete=models.CASCADE)
|
|
||||||
channel_name = models.CharField(max_length=50, blank=False)
|
|
||||||
deck = models.ForeignKey(Deck, on_delete=models.CASCADE)
|
|
||||||
score = models.IntegerField()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.player.name} in que with score {self.score}"
|
|
||||||
|
|
||||||
|
|
||||||
class Room(models.Model):
|
|
||||||
slug = models.SlugField(max_length=16, unique=True)
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
ended = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"room with slug {self.slug}"
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerInRoom(models.Model):
|
|
||||||
player = models.OneToOneField(Player, unique=True, on_delete=models.CASCADE)
|
|
||||||
room = models.ForeignKey(Room, on_delete=models.CASCADE, related_name="players")
|
|
||||||
first = models.BooleanField()
|
|
||||||
score = models.IntegerField(blank=False)
|
|
||||||
deck = models.ForeignKey(Deck, on_delete=models.CASCADE, related_name="decks")
|
|
||||||
online = models.BooleanField(default=False)
|
|
||||||
channel_name = models.CharField(max_length=50, blank=True, null=True)
|
|
||||||
|
|
||||||
def get_state(self):
|
|
||||||
return GameState.objects.filter(player=self.player, room=self.room).last()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.player.name} in room {self.room.slug}"
|
|
||||||
|
|
||||||
|
|
||||||
class GameState(models.Model):
|
|
||||||
room = models.ForeignKey(Room, on_delete=models.CASCADE)
|
|
||||||
player = models.ForeignKey(Player, on_delete=models.CASCADE)
|
|
||||||
round = models.IntegerField(blank=False)
|
|
||||||
message = models.CharField(max_length=100, blank=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ["room", "player", "round"]
|
|
|
@ -1,8 +0,0 @@
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from . import consumers
|
|
||||||
|
|
||||||
websocket_urlpatterns = [
|
|
||||||
path("room/", consumers.QueueConsumer.as_asgi()),
|
|
||||||
path("room/<str:room_name>", consumers.RoomConsumer.as_asgi()),
|
|
||||||
]
|
|
|
@ -1,45 +0,0 @@
|
||||||
from asgiref.sync import sync_to_async
|
|
||||||
from random import randint
|
|
||||||
|
|
||||||
from common.generators import generate_charset
|
|
||||||
from game.models import Player
|
|
||||||
from room.models import Room, PlayerInRoom, GameState
|
|
||||||
|
|
||||||
|
|
||||||
@sync_to_async
|
|
||||||
def create_room(
|
|
||||||
deck_id_1: int,
|
|
||||||
player_id_1: int,
|
|
||||||
player_score_1: int,
|
|
||||||
deck_id_2: int,
|
|
||||||
player_id_2: int,
|
|
||||||
player_score_2: int,
|
|
||||||
) -> str:
|
|
||||||
room = Room.objects.create(slug=generate_charset(16))
|
|
||||||
player_1 = Player.objects.get(id=player_id_1)
|
|
||||||
player_2 = Player.objects.get(id=player_id_2)
|
|
||||||
|
|
||||||
first_player = randint(1, 2)
|
|
||||||
|
|
||||||
PlayerInRoom.objects.create(
|
|
||||||
player=player_1,
|
|
||||||
room=room,
|
|
||||||
score=player_score_1,
|
|
||||||
deck_id=deck_id_1,
|
|
||||||
first=first_player == 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
PlayerInRoom.objects.create(
|
|
||||||
player=player_2,
|
|
||||||
room=room,
|
|
||||||
score=player_score_2,
|
|
||||||
deck_id=deck_id_2,
|
|
||||||
first=first_player == 2,
|
|
||||||
)
|
|
||||||
GameState.objects.create(
|
|
||||||
room=room, player=player_1, round=0, message="Game started"
|
|
||||||
)
|
|
||||||
GameState.objects.create(
|
|
||||||
room=room, player=player_2, round=0, message="Game started"
|
|
||||||
)
|
|
||||||
return room.slug
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
Loading…
Reference in New Issue
Block a user