diff --git a/server/populate_data.py b/server/populate_data.py index 8861d45..c16f006 100644 --- a/server/populate_data.py +++ b/server/populate_data.py @@ -13,6 +13,7 @@ from server.models import ( PlayerGameData, GuildGameDataSchema, WowClasses, + User, ) @@ -28,10 +29,16 @@ def populate_guilds(): g = Guild.objects.get_or_create(name=guild)[0] -def populate_players(): - players = ["Pixel", "Zman", "Skip"] - for player in players: - p = Player.objects.get_or_create(name=player)[0] +def populate_users(): + users = [ + {"username": "Skip", "password": "password"}, + {"username": "Zman", "password": "password"}, + {"username": "Pixel", "password": "password"}, + ] + for user in users: + u = User.objects.get_or_create(username=user["username"])[0] + u.set_password(user["password"]) + u.save() def populate_player_guilds(): @@ -189,12 +196,12 @@ def populate_guild_game_data_schema(): # get or create player game data p1 = PlayerGameData.objects.get_or_create( player=Player.objects.get(name=wow_gamer1["playerName"]), - game=Game.objects.get(name="World of Warcraft"), + schema=s, data=wow_gamer1, )[0] p2 = PlayerGameData.objects.get_or_create( player=Player.objects.get(name=wow_gamer2["playerName"]), - game=Game.objects.get(name="World of Warcraft"), + schema=s, data=wow_gamer2, )[0] @@ -208,7 +215,7 @@ def populate_guild_game_data_schema(): # get or create player game data p1 = PlayerGameData.objects.get_or_create( player=Player.objects.get(name=csgo_epic_nerd1["playerName"]), - game=Game.objects.get(name="Counter Strike: Global Offensive"), + schema=s, data=csgo_epic_nerd1, )[0] @@ -217,7 +224,7 @@ def populate(): with transaction.atomic(): populate_games() populate_guilds() - populate_players() + populate_users() populate_player_guilds() populate_wow_classes() populate_guild_game_data_schema() diff --git a/server/server/migrations/0001_initial.py b/server/server/migrations/0001_initial.py index cb1c9d6..59603aa 100644 --- a/server/server/migrations/0001_initial.py +++ b/server/server/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# Generated by Django 4.2.7 on 2023-11-30 20:12 +# Generated by Django 4.2.7 on 2023-12-05 19:19 +from django.conf import settings from django.db import migrations, models import django.db.models.deletion @@ -9,13 +10,14 @@ class Migration(migrations.Migration): initial = True dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name='Game', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.AutoField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=200)), ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), ], @@ -27,7 +29,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Guild', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.AutoField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=200)), ('tag', models.CharField(max_length=10)), ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), @@ -37,13 +39,26 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Guilds', }, ), + migrations.CreateModel( + name='GuildGameDataSchema', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('schema', models.JSONField()), + ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schemas', to='server.game')), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schemas', to='server.guild')), + ], + options={ + 'verbose_name': 'Guild Game Data Schema', + 'verbose_name_plural': 'Guild Game Data Schemas', + }, + ), migrations.CreateModel( name='Player', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.AutoField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=200)), ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), - ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='players', to='server.guild')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='player', to=settings.AUTH_USER_MODEL)), ], options={ 'verbose_name': 'Player', @@ -51,43 +66,43 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='PlayerGameProfile', + name='WowClasses', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=200)), ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), - ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='player_profiles', to='server.game')), + ], + options={ + 'verbose_name': 'WoW Class', + 'verbose_name_plural': 'WoW Classes', + }, + ), + migrations.CreateModel( + name='PlayerGuild', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('role', models.CharField(default='member', max_length=200)), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='players', to='server.guild')), + ('player', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guilds', to='server.player')), + ], + options={ + 'verbose_name': 'Player Guild', + 'verbose_name_plural': 'Player Guilds', + }, + ), + migrations.CreateModel( + name='PlayerGameData', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), + ('data', models.JSONField()), ('player', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='game_profiles', to='server.player')), + ('schema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='player_game_data', to='server.guildgamedataschema')), ], options={ - 'verbose_name': 'Player Game Profile', - 'verbose_name_plural': 'Player Game Profiles', - }, - ), - migrations.CreateModel( - name='WowProfile', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('level', models.IntegerField()), - ('class_name', models.CharField(max_length=200)), - ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), - ('player_game_profile', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='wow_profile', to='server.playergameprofile')), - ], - options={ - 'verbose_name': 'WoW Profile', - 'verbose_name_plural': 'WoW Profiles', - }, - ), - migrations.CreateModel( - name='ValorantProfile', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rank', models.CharField(max_length=200)), - ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), - ('player_game_profile', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='valorant_profile', to='server.playergameprofile')), - ], - options={ - 'verbose_name': 'Valorant Profile', - 'verbose_name_plural': 'Valorant Profiles', + 'verbose_name': 'Player Game Data', + 'verbose_name_plural': 'Player Game Data', }, ), ] diff --git a/server/server/migrations/0002_alter_game_id_alter_guild_id_alter_player_id_and_more.py b/server/server/migrations/0002_alter_game_id_alter_guild_id_alter_player_id_and_more.py deleted file mode 100644 index dc880d9..0000000 --- a/server/server/migrations/0002_alter_game_id_alter_guild_id_alter_player_id_and_more.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-30 21:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('server', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='game', - name='id', - field=models.AutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='guild', - name='id', - field=models.AutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='player', - name='id', - field=models.AutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='playergameprofile', - name='id', - field=models.AutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='valorantprofile', - name='id', - field=models.AutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='wowprofile', - name='id', - field=models.AutoField(primary_key=True, serialize=False), - ), - ] diff --git a/server/server/migrations/0003_remove_player_guild_playerguild.py b/server/server/migrations/0003_remove_player_guild_playerguild.py deleted file mode 100644 index d0822e2..0000000 --- a/server/server/migrations/0003_remove_player_guild_playerguild.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-30 21:55 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('server', '0002_alter_game_id_alter_guild_id_alter_player_id_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='player', - name='guild', - ), - migrations.CreateModel( - name='PlayerGuild', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), - ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='players', to='server.guild')), - ('player', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guilds', to='server.player')), - ], - options={ - 'verbose_name': 'Player Guild', - 'verbose_name_plural': 'Player Guilds', - }, - ), - ] diff --git a/server/server/migrations/0004_guildgamedataschema_playergamedata_wowclasses_and_more.py b/server/server/migrations/0004_guildgamedataschema_playergamedata_wowclasses_and_more.py deleted file mode 100644 index b28c745..0000000 --- a/server/server/migrations/0004_guildgamedataschema_playergamedata_wowclasses_and_more.py +++ /dev/null @@ -1,75 +0,0 @@ -# Generated by Django 4.2.7 on 2023-12-05 14:47 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('server', '0003_remove_player_guild_playerguild'), - ] - - operations = [ - migrations.CreateModel( - name='GuildGameDataSchema', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('schema', models.JSONField()), - ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schemas', to='server.game')), - ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='schemas', to='server.guild')), - ], - options={ - 'verbose_name': 'Guild Game Data Schema', - 'verbose_name_plural': 'Guild Game Data Schemas', - }, - ), - migrations.CreateModel( - name='PlayerGameData', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), - ('data', models.JSONField()), - ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='player_profiles', to='server.game')), - ('player', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='game_profiles', to='server.player')), - ], - options={ - 'verbose_name': 'Player Game Data', - 'verbose_name_plural': 'Player Game Data', - }, - ), - migrations.CreateModel( - name='WowClasses', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=200)), - ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date created')), - ], - options={ - 'verbose_name': 'WoW Class', - 'verbose_name_plural': 'WoW Classes', - }, - ), - migrations.RemoveField( - model_name='valorantprofile', - name='player_game_profile', - ), - migrations.RemoveField( - model_name='wowprofile', - name='player_game_profile', - ), - migrations.AddField( - model_name='playerguild', - name='role', - field=models.CharField(default='member', max_length=200), - ), - migrations.DeleteModel( - name='PlayerGameProfile', - ), - migrations.DeleteModel( - name='ValorantProfile', - ), - migrations.DeleteModel( - name='WowProfile', - ), - ] diff --git a/server/server/models.py b/server/server/models.py index 3f107b7..b1eb558 100644 --- a/server/server/models.py +++ b/server/server/models.py @@ -1,4 +1,7 @@ from django.db import models +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver class Guild(models.Model): @@ -22,9 +25,11 @@ class Guild(models.Model): class Player(models.Model): """ This model is used to store the player data. + Players have a one to one relationship with users. """ id = models.AutoField(primary_key=True) + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="player") name = models.CharField(max_length=200) creation_date = models.DateTimeField("date created", auto_now_add=True) @@ -36,6 +41,15 @@ class Player(models.Model): verbose_name_plural = "Players" +# Signal to create/update player when user is created/updated +@receiver(post_save, sender=User) +def create_or_update_user_player(sender, instance, created, **kwargs): + if created: + Player.objects.create(user=instance, name=instance.username) # Add default values for other fields if necessary + else: + instance.player.save() # Save the player instance if the user is updated + + class PlayerGuild(models.Model): """ This model is used to store the player's guild data. @@ -85,8 +99,10 @@ class PlayerGameData(models.Model): player = models.ForeignKey( Player, on_delete=models.CASCADE, related_name="game_profiles" ) - game = models.ForeignKey( - Game, on_delete=models.CASCADE, related_name="player_profiles" + schema = models.ForeignKey( + "GuildGameDataSchema", + on_delete=models.CASCADE, + related_name="player_game_data", ) creation_date = models.DateTimeField("date created", auto_now_add=True) data = models.JSONField() diff --git a/server/server/serializers.py b/server/server/serializers.py index 5c79f44..3e82d07 100644 --- a/server/server/serializers.py +++ b/server/server/serializers.py @@ -36,16 +36,15 @@ class GameSerializer(serializers.ModelSerializer): class PlayerGameDataSerializer(serializers.ModelSerializer): class Meta: model = PlayerGameData - fields = ["id", "player", "game", "creation_date", "data"] + fields = ["id", "player", "schema", "creation_date", "data"] read_only_fields = ["id", "creation_date"] def validate_data(self, value): - game_id = self.initial_data["game"] try: - schema_obj = GuildGameDataSchema.objects.get( - game=game_id, guild=self.initial_data["guild"] - ) - schema = schema_obj.schema + # get the schema for the player's guild and game + schema = GuildGameDataSchema.objects.get( + guild=self.context["guild"], game=self.context["game"] + ).schema validate(value, schema) @@ -64,6 +63,12 @@ class GuildGameDataSchemaSerializer(serializers.ModelSerializer): model = GuildGameDataSchema fields = ["id", "guild", "game", "schema"] read_only_fields = ["id"] + + def validate_schema(self, value): + # ensure schema has no more than 15 fields + # this is just an example it wont actually be this simple + if len(value["properties"]) > 15: + raise serializers.ValidationError("Schema cannot have more than 15 fields") class PlayerGuildSerializer(serializers.ModelSerializer): @@ -78,3 +83,8 @@ class WowClassesSerializer(serializers.ModelSerializer): model = WowClasses fields = ["id", "name", "creation_date"] read_only_fields = ["id", "creation_date"] + + +class LoginSerializer(serializers.Serializer): + username = serializers.CharField(required=True) + password = serializers.CharField(write_only=True, required=True) \ No newline at end of file diff --git a/server/server/settings.py b/server/server/settings.py index 75fa6c9..db37f74 100644 --- a/server/server/settings.py +++ b/server/server/settings.py @@ -87,6 +87,14 @@ TEMPLATES = [ WSGI_APPLICATION = "server.wsgi.application" +# REST Framework +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ] +} + # Database # https://docs.djangoproject.com/en/4.2/ref/settings/#databases diff --git a/server/server/urls.py b/server/server/urls.py index 751fe06..6d53d2b 100644 --- a/server/server/urls.py +++ b/server/server/urls.py @@ -13,6 +13,7 @@ from .views import ( GuildGameDataSchemaViewSet, PlayerGuildViewSet, WowClassesViewSet, + LoginView, ) router = DefaultRouter() @@ -42,6 +43,7 @@ urlpatterns = [ path("", hello_world, name="index"), path("admin/", admin.site.urls), path("api/", include(router.urls)), + path("login/", LoginView.as_view(), name="login"), # Swagger re_path( r"^swagger(?P\.json|\.yaml)$", diff --git a/server/server/views.py b/server/server/views.py index f3456aa..20d6e96 100644 --- a/server/server/views.py +++ b/server/server/views.py @@ -1,5 +1,11 @@ from django.http import HttpResponse -from rest_framework import viewsets +from rest_framework import viewsets, status +from django.contrib.auth import authenticate, login +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser +from drf_yasg.utils import swagger_auto_schema +from drf_yasg import openapi from .models import ( Game, Guild, @@ -17,43 +23,75 @@ from .serializers import ( PlayerGameDataSerializer, GuildGameDataSchemaSerializer, WowClassesSerializer, + LoginSerializer, ) +class LoginView(APIView): + permission_classes = [AllowAny] + @swagger_auto_schema( + operation_description="Login to the system", + request_body=LoginSerializer, + responses={ + status.HTTP_200_OK: openapi.Response('Login Successful'), + status.HTTP_401_UNAUTHORIZED: openapi.Response('Invalid Credentials'), + } + ) + def post(self, request, format=None): + serializer = LoginSerializer(data=request.data) + if serializer.is_valid(): + username = serializer.validated_data['username'] + password = serializer.validated_data['password'] + user = authenticate(request, username=username, password=password) + if user is not None: + login(request, user) + return Response({'detail': 'Login successful'}, status=status.HTTP_200_OK) + else: + return Response({'detail': 'Invalid Credentials'}, status=status.HTTP_401_UNAUTHORIZED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def hello_world(request): return HttpResponse("Hello, World!") class GameViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] queryset = Game.objects.all() serializer_class = GameSerializer class GuildViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] queryset = Guild.objects.all() serializer_class = GuildSerializer class PlayerViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] queryset = Player.objects.all() serializer_class = PlayerSerializer class PlayerGameDataViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] queryset = PlayerGameData.objects.all() serializer_class = PlayerGameDataSerializer class GuildGameDataSchemaViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] queryset = GuildGameDataSchema.objects.all() serializer_class = GuildGameDataSchemaSerializer class PlayerGuildViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] queryset = PlayerGuild.objects.all() serializer_class = PlayerGuildSerializer class WowClassesViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] queryset = WowClasses.objects.all() serializer_class = WowClassesSerializer