From 91802c9ce15878f2ad0018beb18f57c899cabc88 Mon Sep 17 00:00:00 2001 From: TUTOR03 Date: Sat, 28 Nov 2020 23:48:34 +0500 Subject: [PATCH] add Game list, single game display, invites to teams --- backend/back/admin.py | 3 + .../migrations/0004_auto_20201128_2120.py | 50 +++++++++++ .../migrations/0005_auto_20201128_2216.py | 42 +++++++++ .../back/migrations/0006_invitationtoken.py | 22 +++++ backend/back/models.py | 16 ++++ backend/back/serializers.py | 24 ++++- backend/back/urls.py | 5 +- backend/back/views.py | 90 +++++++++++++++++-- backend/requirements.txt | 26 ++++++ 9 files changed, 268 insertions(+), 10 deletions(-) create mode 100644 backend/back/migrations/0004_auto_20201128_2120.py create mode 100644 backend/back/migrations/0005_auto_20201128_2216.py create mode 100644 backend/back/migrations/0006_invitationtoken.py create mode 100644 backend/requirements.txt diff --git a/backend/back/admin.py b/backend/back/admin.py index ff9293f..abbbb6f 100644 --- a/backend/back/admin.py +++ b/backend/back/admin.py @@ -4,4 +4,7 @@ from . import models admin.site.register(models.Profile) admin.site.register(models.CheckPoint) admin.site.register(models.Game) +admin.site.register(models.InvitationToken) +admin.site.register(models.Gamer) +admin.site.register(models.Team) # Register your models here. diff --git a/backend/back/migrations/0004_auto_20201128_2120.py b/backend/back/migrations/0004_auto_20201128_2120.py new file mode 100644 index 0000000..b26fe06 --- /dev/null +++ b/backend/back/migrations/0004_auto_20201128_2120.py @@ -0,0 +1,50 @@ +# Generated by Django 3.1.3 on 2020-11-28 16:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('back', '0003_auto_20201128_1939'), + ] + + operations = [ + migrations.CreateModel( + name='Game_in_progress', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active', models.BooleanField(default=False)), + ('finished', models.BooleanField(default=False)), + ('start', models.DateTimeField(blank=True, null=True)), + ('end', models.DateTimeField(blank=True, null=True)), + ], + ), + migrations.AlterField( + model_name='game', + name='slug', + field=models.SlugField(blank=True, unique=True), + ), + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('captain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='back.profile')), + ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.game_in_progress')), + ], + ), + migrations.CreateModel( + name='Gamer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.profile')), + ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gamers', to='back.team')), + ], + ), + migrations.AddField( + model_name='game_in_progress', + name='game', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.game'), + ), + ] diff --git a/backend/back/migrations/0005_auto_20201128_2216.py b/backend/back/migrations/0005_auto_20201128_2216.py new file mode 100644 index 0000000..be10fd8 --- /dev/null +++ b/backend/back/migrations/0005_auto_20201128_2216.py @@ -0,0 +1,42 @@ +# Generated by Django 3.1.3 on 2020-11-28 17:16 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('back', '0004_auto_20201128_2120'), + ] + + operations = [ + migrations.AddField( + model_name='team', + name='active', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='team', + name='end', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='team', + name='finished', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='team', + name='start', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='team', + name='game', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.game'), + ), + migrations.DeleteModel( + name='Game_in_progress', + ), + ] diff --git a/backend/back/migrations/0006_invitationtoken.py b/backend/back/migrations/0006_invitationtoken.py new file mode 100644 index 0000000..f074d03 --- /dev/null +++ b/backend/back/migrations/0006_invitationtoken.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1.3 on 2020-11-28 18:17 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('back', '0005_auto_20201128_2216'), + ] + + operations = [ + migrations.CreateModel( + name='InvitationToken', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('token', models.CharField(max_length=200)), + ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='back.team')), + ], + ), + ] diff --git a/backend/back/models.py b/backend/back/models.py index 155799d..71ac7ad 100644 --- a/backend/back/models.py +++ b/backend/back/models.py @@ -37,6 +37,22 @@ class CheckPoint(models.Model): start = models.BooleanField(default = False) address = models.CharField(max_length = 200, blank = True, null = True) +class Team(models.Model): + game = models.ForeignKey(Game, on_delete= models.CASCADE, blank = False, null = False) + active = models.BooleanField(default = False) + finished = models.BooleanField(default = False) + start = models.DateTimeField(blank = True, null = True) + end = models.DateTimeField(blank = True, null = True) + captain = models.ForeignKey(Profile, on_delete = models.CASCADE, blank = True, null = True) + +class InvitationToken(models.Model): + token = models.CharField(max_length = 200, blank = False, null = False) + team = models.ForeignKey(Team, on_delete = models.CASCADE, blank = False, null = False) + +class Gamer(models.Model): + profile = models.ForeignKey(Profile, on_delete = models.CASCADE, blank = False, null = False) + team = models.ForeignKey(Team, on_delete = models.CASCADE, related_name = 'gamers', blank = False, null = False) + @receiver(post_save, sender = User) def create_profile(sender, instance, created, **kwargs): if(created): diff --git a/backend/back/serializers.py b/backend/back/serializers.py index e7faf4e..968e9f8 100644 --- a/backend/back/serializers.py +++ b/backend/back/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from django.contrib.auth.models import User -from .models import Game +from .models import Game, Team, Gamer class GameListSerializer(serializers.ModelSerializer): class Meta: @@ -17,6 +17,28 @@ class UserRegisterSerializer(serializers.ModelSerializer): 'email' ] +class TeamSerializer(serializers.ModelSerializer): + game = serializers.SerializerMethodField() + gamers = serializers.SerializerMethodField() + captain = serializers.SerializerMethodField() + class Meta: + model = Team + fields = [ + 'active', + 'finished', + 'start', + 'end', + 'captain', + 'game', + 'gamers', + ] + def get_game(self, obj): + return(GameListSerializer(obj.game).data) + def get_gamers(self, obj): + return(list(map(lambda ob:ob.profile.user.username,Gamer.objects.filter(team = obj)))) + def get_captain(self, obj): + return(obj.captain.user.username) + class UserResetPasswordSerializer(serializers.ModelSerializer): email = serializers.EmailField(required=True) class Meta: diff --git a/backend/back/urls.py b/backend/back/urls.py index 5396778..89bfe90 100644 --- a/backend/back/urls.py +++ b/backend/back/urls.py @@ -4,8 +4,9 @@ from . import views urlpatterns = [ path('games', views.ListGamesAPIView.as_view(), name = 'ListGames'), - path('game/', views.SingleGameAPIView.as_view(), name = 'SingleGame'), - # path('game//take_part',), + path('game/', views.SingleGameAPIView, name = 'SingleGame'), + path('game//take_part',views.GameTakePartAPIView, name = 'GameTakePartAPIView'), + path('game//join_team/',views.JoinTeamAPIView, name = 'JoinTeam'), path('register', views.UserRegisterAPIView, name = 'UserRegister'), path('login', obtain_auth_token, name = 'UserLogin'), path('logout', views.UserLogoutAPIView, name = 'UserLogout'), diff --git a/backend/back/views.py b/backend/back/views.py index 2902372..88f6e99 100644 --- a/backend/back/views.py +++ b/backend/back/views.py @@ -10,12 +10,13 @@ from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.template.loader import render_to_string from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.core.mail import EmailMultiAlternatives -from .serializers import UserRegisterSerializer, UserResetPasswordSerializer, UserPasswordSerializer, GameListSerializer +from .serializers import UserRegisterSerializer, UserResetPasswordSerializer, UserPasswordSerializer, GameListSerializer, TeamSerializer from django.utils.html import strip_tags from django.contrib.auth.password_validation import validate_password from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter, SearchFilter -from .models import Game +from .models import Game, Team, Gamer, Profile, InvitationToken +from hashlib import sha1 import six import threading @@ -23,6 +24,12 @@ class EmailTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): return(six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active)) +# class InviteTokenGenerator(PasswordResetTokenGenerator): +# def make_token(self, user,): +# return self._make_token_with_timestamp(user, self._num_seconds(self._now())) +# def _make_hash_value(self, user, timestamp): +# return(six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active)) + email_token_gen = EmailTokenGenerator() password_token_gen = PasswordResetTokenGenerator() @@ -37,11 +44,80 @@ class ListGamesAPIView(generics.ListAPIView): def get_queryset(self): return Game.objects.filter(active = True) -class SingleGameAPIView(generics.RetrieveAPIView): - serializer_class = GameListSerializer - permission_classes = [permissions.IsAuthenticated] - lookup_field = 'slug' - queryset = Game.objects.all() +@api_view(['GET']) +@permission_classes([permissions.IsAuthenticated]) +def SingleGameAPIView(request, slug): + game = Game.objects.filter(slug = slug) + if(game.exists()): + profile = Profile.objects.get(user = request.user) + game = game.first() + teams = Team.objects.filter(game = game) + team = [] + for team_i in teams: + if(Gamer.objects.filter(profile = profile, team = team_i).exists()): + team = team_i + break + if(team): + serializer = TeamSerializer(team) + data = serializer.data + gamers = data['gamers'] + can_start = False + invite_token = '' + if(team.captain == profile and (not game.co_op or len(gamers) == game.max_gamers)): + can_start = True + if(team.captain == profile): + invite = InvitationToken.objects.filter(team = team) + if(invite.exists()): + invite = invite.first() + invite_token = invite.token + return Response({'ok':True, 'can_take_part':False, 'can_start':can_start, 'invite_token':invite_token, **data}, status = status.HTTP_200_OK) + else: + serializer = GameListSerializer(game) + data = serializer.data + return Response({'ok':True, 'can_take_part':True, **serializer.data}, status = status.HTTP_200_OK) + return Response({'ok':False, 'error':'Game does not exist'},status = status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([permissions.IsAuthenticated]) +def GameTakePartAPIView(request, slug): + game = Game.objects.filter(slug = slug) + if(game.exists()): + game = game.first() + profile = Profile.objects.get(user = request.user) + teams = Team.objects.filter(game = game, captain = profile) + team = [] + for team_i in teams: + if(Gamer.objects.filter(profile = profile, team = team_i).exists()): + team = team_i + break + if(not team): + team = Team.objects.create(game = game, captain = profile) + if(game.co_op): + invite_token = sha1(bytes(f'{team.id}{game.id}{profile.id}{profile.user.username}','utf-8')).hexdigest()[:200] + InvitationToken.objects.create(team = team, token = invite_token) + gamer = Gamer.objects.create(team = team, profile = profile) + return Response({'ok':True}, status = status.HTTP_200_OK) + return Response({'ok':False, 'error':'You alredy take part in this game'},status = status.HTTP_400_BAD_REQUEST) + return Response({'ok':False, 'error':'Game does not exist'},status = status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([permissions.IsAuthenticated]) +def JoinTeamAPIView(request, slug, token): + game = Game.objects.filter(slug = slug) + if(game.exists()): + invite = InvitationToken.objects.filter(token = token) + if(invite.exists()): + profile = Profile.objects.get(user = request.user) + invite = invite.first() + team = invite.team + serializer = TeamSerializer(team) + gamers = serializer.data['gamers'] + if(len(gamers) < team.game.max_gamers): + Gamer.objects.create(team = team, profile = profile) + return Response({'ok':True},status = status.HTTP_200_OK) + return Response({'ok':False, 'error':'Game finished or team is full'},status = status.HTTP_400_BAD_REQUEST) + return Response({'ok':False, 'error':'Invalid invite link'},status = status.HTTP_400_BAD_REQUEST) + return Response({'ok':False, 'error':'Game does not exist'},status = status.HTTP_400_BAD_REQUEST) @api_view(['POST']) @permission_classes([~permissions.IsAuthenticated]) diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..4a736eb --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,26 @@ +asgiref==3.3.1 +certifi==2020.11.8 +cffi==1.14.4 +chardet==3.0.4 +cryptography==3.2.1 +defusedxml==0.7.0rc1 +Django==3.1.3 +django-braces==1.14.0 +django-filter==2.4.0 +django-oauth-toolkit==1.3.3 +django-rest-framework-social-oauth2==1.1.0 +djangorestframework==3.12.2 +idna==2.10 +oauthlib==3.1.0 +psycopg2==2.8.6 +pycparser==2.20 +PyJWT==1.7.1 +python3-openid==3.2.0 +pytz==2020.4 +requests==2.25.0 +requests-oauthlib==1.3.0 +six==1.15.0 +social-auth-app-django==4.0.0 +social-auth-core==3.3.3 +sqlparse==0.4.1 +urllib3==1.26.2