mirror of
https://github.com/evgen-app/chess_rpg_backend.git
synced 2025-02-16 19:40:38 +03:00
added deck model and creation endpoint
This commit is contained in:
parent
acc37f8e82
commit
ba04ce8347
0
game/api/__init__.py
Normal file
0
game/api/__init__.py
Normal file
0
game/api/v1/__init__.py
Normal file
0
game/api/v1/__init__.py
Normal file
94
game/api/v1/serializers.py
Normal file
94
game/api/v1/serializers.py
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from game.models import Hero, Player, HeroInDeck, Deck
|
||||||
|
|
||||||
|
|
||||||
|
class CreateHeroSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Hero
|
||||||
|
fields = (
|
||||||
|
"type",
|
||||||
|
"idle_img",
|
||||||
|
"attack_img",
|
||||||
|
"die_img",
|
||||||
|
"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 DeckCreateSerializer(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):
|
||||||
|
for x in value:
|
||||||
|
if not (hero := Hero.objects.filter(uuid=x)):
|
||||||
|
raise ValidationError(f"Hero with uuid {x} doesn't exist")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class GetDeckSerializer(serializers.ModelSerializer):
|
||||||
|
heroes = ListHeroSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Deck
|
||||||
|
fields = ("player_uuid", "heroes")
|
||||||
|
|
||||||
|
def get_heroes(self, val):
|
||||||
|
print(val)
|
|
@ -1,16 +1,29 @@
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from rest_framework.generics import GenericAPIView, UpdateAPIView
|
from rest_framework.generics import GenericAPIView, UpdateAPIView
|
||||||
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, ListModelMixin
|
from rest_framework.mixins import (
|
||||||
|
CreateModelMixin,
|
||||||
|
RetrieveModelMixin,
|
||||||
|
ListModelMixin,
|
||||||
|
DestroyModelMixin,
|
||||||
|
UpdateModelMixin,
|
||||||
|
)
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from game.authentication import PlayerAuthentication
|
from game.authentication import PlayerAuthentication
|
||||||
from game.models import Hero
|
from game.models import Hero
|
||||||
from game.serializers import CreateHeroSerializer, GetHeroSerializer, CreatePlayerView
|
from game.api.v1.serializers import (
|
||||||
|
CreateHeroSerializer,
|
||||||
|
GetHeroSerializer,
|
||||||
|
CreatePlayerSerializer,
|
||||||
|
ListHeroSerializer,
|
||||||
|
DeckCreateSerializer,
|
||||||
|
GetDeckSerializer,
|
||||||
|
)
|
||||||
from game.services.jwt import sign_jwt
|
from game.services.jwt import sign_jwt
|
||||||
|
|
||||||
|
|
||||||
class CreateHeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
|
class ListCreateHeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
|
||||||
authentication_classes = (PlayerAuthentication,)
|
authentication_classes = (PlayerAuthentication,)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
|
@ -27,7 +40,7 @@ class CreateHeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.request.method == "GET":
|
if self.request.method == "GET":
|
||||||
return GetHeroSerializer
|
return ListHeroSerializer
|
||||||
else:
|
else:
|
||||||
return CreateHeroSerializer
|
return CreateHeroSerializer
|
||||||
|
|
||||||
|
@ -52,7 +65,7 @@ class RetrieveHeroView(RetrieveModelMixin, UpdateAPIView, GenericAPIView):
|
||||||
|
|
||||||
|
|
||||||
class PlayerCreateView(GenericAPIView, CreateModelMixin):
|
class PlayerCreateView(GenericAPIView, CreateModelMixin):
|
||||||
serializer_class = CreatePlayerView
|
serializer_class = CreatePlayerSerializer
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
return serializer.save()
|
return serializer.save()
|
||||||
|
@ -65,4 +78,35 @@ class PlayerCreateView(GenericAPIView, CreateModelMixin):
|
||||||
# TODO: add JTI to refresh token
|
# TODO: add JTI to refresh token
|
||||||
access_jwt = sign_jwt({"id": instance.id, "type": "access"}, t_life=3600)
|
access_jwt = sign_jwt({"id": instance.id, "type": "access"}, t_life=3600)
|
||||||
refresh_jwt = sign_jwt({"id": instance.id, "type": "refresh"})
|
refresh_jwt = sign_jwt({"id": instance.id, "type": "refresh"})
|
||||||
return Response({"access_token": access_jwt, "refresh_token": refresh_jwt}, status=status.HTTP_201_CREATED)
|
return Response(
|
||||||
|
{"access_token": access_jwt, "refresh_token": refresh_jwt},
|
||||||
|
status=status.HTTP_201_CREATED,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DeckCreateView(GenericAPIView, CreateModelMixin):
|
||||||
|
serializer_class = DeckCreateSerializer
|
||||||
|
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)
|
||||||
|
return Response(heroes_list.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
|
class RetireUpdateDeleteDeckView(
|
||||||
|
RetrieveHeroView, DestroyModelMixin, UpdateModelMixin, GenericAPIView
|
||||||
|
):
|
||||||
|
lookup_field = "pk"
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.request.method == "GET":
|
||||||
|
return GetDeckSerializer
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return self.retrieve(request, *args, **kwargs)
|
|
@ -7,7 +7,9 @@ from .services.jwt import read_jwt
|
||||||
class PlayerAuthentication(authentication.BaseAuthentication):
|
class PlayerAuthentication(authentication.BaseAuthentication):
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
|
|
||||||
if "Authorization" not in request.headers or not (token := request.headers["Authorization"]):
|
if "Authorization" not in request.headers or not (
|
||||||
|
token := request.headers["Authorization"]
|
||||||
|
):
|
||||||
raise exceptions.AuthenticationFailed("No credentials provided.")
|
raise exceptions.AuthenticationFailed("No credentials provided.")
|
||||||
|
|
||||||
t = read_jwt(token)
|
t = read_jwt(token)
|
||||||
|
|
57
game/migrations/0001_initial.py
Normal file
57
game/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -41,7 +41,10 @@ class Player(models.Model):
|
||||||
hero.player = self
|
hero.player = self
|
||||||
hero.type = t
|
hero.type = t
|
||||||
|
|
||||||
with open("/home/sanspie/Projects/chess_rpg_backend/media/dummy.jpg", "rb+") as file:
|
# TODO: create image pool to generate heroes (awaiting for designer)
|
||||||
|
with open(
|
||||||
|
"/home/sanspie/Projects/chess_rpg_backend/media/dummy.jpg", "rb+"
|
||||||
|
) as file:
|
||||||
hero.idle_img = File(file, name="dummy.jpg")
|
hero.idle_img = File(file, name="dummy.jpg")
|
||||||
hero.attack_img = File(file, name="dummy.jpg")
|
hero.attack_img = File(file, name="dummy.jpg")
|
||||||
hero.die_img = File(file, name="dummy.jpg")
|
hero.die_img = File(file, name="dummy.jpg")
|
||||||
|
@ -58,6 +61,8 @@ class Player(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = [models.Index(fields=["ton_wallet"])]
|
indexes = [models.Index(fields=["ton_wallet"])]
|
||||||
ordering = ["-created"]
|
ordering = ["-created"]
|
||||||
|
|
||||||
|
db_table = "player"
|
||||||
verbose_name = "player"
|
verbose_name = "player"
|
||||||
verbose_name_plural = "players"
|
verbose_name_plural = "players"
|
||||||
|
|
||||||
|
@ -65,7 +70,9 @@ class Player(models.Model):
|
||||||
class Hero(models.Model):
|
class Hero(models.Model):
|
||||||
"""Model to store heroes and their stats, connected to player"""
|
"""Model to store heroes and their stats, connected to player"""
|
||||||
|
|
||||||
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
uuid = models.UUIDField(
|
||||||
|
default=uuid.uuid4, editable=False, unique=True, primary_key=True
|
||||||
|
)
|
||||||
player = models.ForeignKey(
|
player = models.ForeignKey(
|
||||||
Player,
|
Player,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
@ -94,5 +101,47 @@ class Hero(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = [models.Index(fields=["uuid"])]
|
indexes = [models.Index(fields=["uuid"])]
|
||||||
ordering = ["-added"]
|
ordering = ["-added"]
|
||||||
|
|
||||||
|
db_table = "hero"
|
||||||
verbose_name = "hero"
|
verbose_name = "hero"
|
||||||
verbose_name_plural = "heroes"
|
verbose_name_plural = "heroes"
|
||||||
|
|
||||||
|
|
||||||
|
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} deck"
|
||||||
|
|
||||||
|
def get_heroes(self):
|
||||||
|
return [x.hero for x in HeroInDeck.objects.filter(deck=self)]
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from game.models import Hero, Player
|
|
||||||
|
|
||||||
|
|
||||||
class CreateHeroSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Hero
|
|
||||||
fields = (
|
|
||||||
"type",
|
|
||||||
"idle_img",
|
|
||||||
"attack_img",
|
|
||||||
"die_img",
|
|
||||||
"health",
|
|
||||||
"attack",
|
|
||||||
"speed",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GetHeroSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Hero
|
|
||||||
fields = (
|
|
||||||
"added",
|
|
||||||
"type",
|
|
||||||
"idle_img",
|
|
||||||
"attack_img",
|
|
||||||
"die_img",
|
|
||||||
"health",
|
|
||||||
"attack",
|
|
||||||
"speed",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CreatePlayerView(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Player
|
|
||||||
fields = ("ton_wallet", "name")
|
|
18
game/urls.py
18
game/urls.py
|
@ -1,9 +1,19 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from game.views import CreateHeroView, RetrieveHeroView, PlayerCreateView
|
from game.api.v1.views import (
|
||||||
|
ListCreateHeroView,
|
||||||
|
RetrieveHeroView,
|
||||||
|
PlayerCreateView,
|
||||||
|
DeckCreateView,
|
||||||
|
RetireUpdateDeleteDeckView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("hero/", CreateHeroView.as_view(), name="hero_api_create"),
|
path("v1/hero/", ListCreateHeroView.as_view(), name="hero_api_create"),
|
||||||
path("hero/<uuid:uuid>", RetrieveHeroView.as_view(), name="hero_api_retrieve"),
|
path("v1/hero/<uuid:uuid>", RetrieveHeroView.as_view(), name="hero_api_retrieve"),
|
||||||
path("player/", PlayerCreateView.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:pk>", RetireUpdateDeleteDeckView.as_view(), name="deck_retire_api"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user