diff --git a/api/masteriq/settings.py b/api/masteriq/settings.py index 21f6af2..4d25a53 100644 --- a/api/masteriq/settings.py +++ b/api/masteriq/settings.py @@ -30,7 +30,7 @@ DEBUG = bool(strtobool(os.getenv('DEBUG'))) ALLOWED_HOSTS = [ - os.getenv('BACKEND_HOST') + os.getenv('BACKEND_HOST'), ] CORS_ALLOWED_ORIGINS = [ @@ -38,6 +38,11 @@ os.getenv('BACKEND_URL'), ] +CSRF_TRUSTED_ORIGINS = [ + os.getenv('FRONTEND_URL'), + os.getenv('BACKEND_URL'), +] + CORS_ALLOW_CREDENTIALS = True # Application definition @@ -49,9 +54,10 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'corsheaders', 'masteriqapp', 'rest_framework', - "corsheaders", + ] MIDDLEWARE = [ @@ -155,5 +161,11 @@ IMAGES_FOLDER = os.path.join('data', 'images') DEFAULT_IMAGE = "default.jpeg" +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ) +} +AUTH_USER_MODEL='masteriqapp.CustomUser' # end of file \ No newline at end of file diff --git a/api/masteriqapp/admin.py b/api/masteriqapp/admin.py index ea5d68b..933d5f8 100644 --- a/api/masteriqapp/admin.py +++ b/api/masteriqapp/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from masteriqapp.models import CustomUser # Register your models here. +admin.site.register(CustomUser, UserAdmin) \ No newline at end of file diff --git a/api/masteriqapp/migrations/0001_initial.py b/api/masteriqapp/migrations/0001_initial.py index f9111d6..ae21604 100644 --- a/api/masteriqapp/migrations/0001_initial.py +++ b/api/masteriqapp/migrations/0001_initial.py @@ -1,6 +1,9 @@ -# Generated by Django 5.0.2 on 2024-03-01 12:31 +# Generated by Django 5.0.2 on 2024-04-06 15:02 +import django.contrib.auth.models +import django.contrib.auth.validators import django.db.models.deletion +import django.utils.timezone from django.conf import settings from django.db import migrations, models @@ -10,7 +13,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ @@ -19,10 +22,37 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255)), + ('image_path', models.CharField(default='data\\images\\default.jpeg', max_length=255)), ], ), migrations.CreateModel( - name='Iq', + name='CustomUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='IQ', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('iq', models.IntegerField()), @@ -33,13 +63,18 @@ class Migration(migrations.Migration): migrations.AddField( model_name='category', name='users', - field=models.ManyToManyField(through='masteriqapp.Iq', to=settings.AUTH_USER_MODEL), + field=models.ManyToManyField(through='masteriqapp.IQ', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='customuser', + name='iqs', + field=models.ManyToManyField(through='masteriqapp.IQ', to=settings.AUTH_USER_MODEL), ), migrations.CreateModel( name='Question', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('text', models.CharField(max_length=255)), + ('text', models.CharField(max_length=1024)), ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='masteriqapp.category')), ], ), @@ -47,9 +82,9 @@ class Migration(migrations.Migration): name='Option', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('text', models.CharField(max_length=255)), + ('text', models.CharField(max_length=1024)), ('is_correct', models.BooleanField()), - ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='masteriqapp.question')), + ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='masteriqapp.question')), ], ), ] diff --git a/api/masteriqapp/migrations/0002_auto_20240303_1147.py b/api/masteriqapp/migrations/0002_auto_20240303_1147.py index 986820f..1de3d31 100644 --- a/api/masteriqapp/migrations/0002_auto_20240303_1147.py +++ b/api/masteriqapp/migrations/0002_auto_20240303_1147.py @@ -1,4 +1,5 @@ # Generated by Django 5.0.2 on 2024-03-03 10:47 +import glob from django.db import migrations from django.conf import settings @@ -25,20 +26,37 @@ def load_initial_data(apps, schema_editor): continue question = question_model.objects.create(text=df['Questions'][row], category=category) right_option_text = df['Correct'][row] - option_model.objects.create(text=right_option_text, is_correct=True, question=question) for letter in ['A', 'B', 'C', 'D']: - if pd.isnull(df[letter][row]) or df[letter][row] == right_option_text: + if pd.isnull(df[letter][row]): continue - option_model.objects.create(text=df[letter][row], is_correct=False, question=question) + if df[letter][row] == right_option_text: + option_model.objects.create(text=right_option_text, is_correct=True, question=question) + else: + option_model.objects.create(text=df[letter][row], is_correct=False, question=question) print("Initial data loaded!") +def add_community_category(apps, schema_editor): + category_model = apps.get_model('masteriqapp', 'Category') + category_model.objects.create(name="Community", image_path='data\\images\\community.jpg') +def add_category_images(apps, schema_editor): + category_model = apps.get_model('masteriqapp', 'Category') + for category in category_model.objects.all(): + directory = settings.IMAGES_FOLDER + img_name = category.name.lower().replace(' ', '_') + ".*" + f = os.path.join(directory, img_name) + files = glob.glob(f) + if len(files) > 0: + category.image_path = os.path.join(files[0]) + category.save() class Migration(migrations.Migration): dependencies = [ - ('masteriqapp', '0003_alter_category_users'), + ('masteriqapp', '0001_initial'), ] operations = [ - migrations.RunPython(load_initial_data) + migrations.RunPython(load_initial_data), + migrations.RunPython(add_community_category), + migrations.RunPython(add_category_images) ] diff --git a/api/masteriqapp/migrations/0003_alter_category_users.py b/api/masteriqapp/migrations/0003_alter_category_users.py deleted file mode 100644 index b7662a1..0000000 --- a/api/masteriqapp/migrations/0003_alter_category_users.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.0.2 on 2024-03-05 17:52 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('masteriqapp', '0006_alter_option_question_alter_option_text_and_more'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AlterField( - model_name='category', - name='users', - field=models.ManyToManyField(through='masteriqapp.IQ', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/api/masteriqapp/migrations/0004_auto_20240308_0923.py b/api/masteriqapp/migrations/0004_auto_20240308_0923.py deleted file mode 100644 index 801f42d..0000000 --- a/api/masteriqapp/migrations/0004_auto_20240308_0923.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.0.2 on 2024-03-08 08:23 -import os - -from django.conf import settings -from django.db import migrations -import glob - - -def add_category_images(apps, schema_editor): - category_model = apps.get_model('masteriqapp', 'Category') - - for category in category_model.objects.all(): - directory = settings.IMAGES_FOLDER - img_name = category.name.lower().replace(' ', '_') + ".*" - f = os.path.join(directory, img_name) - files = glob.glob(f) - if len(files) > 0: - category.image_path = os.path.join(files[0]) - category.save() - - -class Migration(migrations.Migration): - dependencies = [ - ('masteriqapp', '0005_category_image_path'), - ] - - operations = [ - migrations.RunPython(add_category_images) - ] diff --git a/api/masteriqapp/migrations/0005_auto_20240314_1738.py b/api/masteriqapp/migrations/0005_auto_20240314_1738.py deleted file mode 100644 index 089c426..0000000 --- a/api/masteriqapp/migrations/0005_auto_20240314_1738.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.0.2 on 2024-03-14 16:38 - -from django.db import migrations - -def add_community_category(apps, schema_editor): - category_model = apps.get_model('masteriqapp', 'Category') - category_model.objects.create(name="Community", image_path='data\\images\\community.jpg') - -class Migration(migrations.Migration): - - dependencies = [ - ('masteriqapp', '0004_auto_20240308_0923'), - ] - - operations = [ - migrations.RunPython(add_community_category) - ] diff --git a/api/masteriqapp/migrations/0005_category_image_path.py b/api/masteriqapp/migrations/0005_category_image_path.py deleted file mode 100644 index 3524835..0000000 --- a/api/masteriqapp/migrations/0005_category_image_path.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.0.2 on 2024-03-08 08:42 -from os import path - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('masteriqapp', '0002_auto_20240303_1147'), - ] - - operations = [ - migrations.AddField( - model_name='category', - name='image_path', - field=models.CharField(default=path.join(settings.IMAGES_FOLDER, 'default.jpeg'), max_length=255), - ), - ] diff --git a/api/masteriqapp/migrations/0006_alter_option_question_alter_option_text_and_more.py b/api/masteriqapp/migrations/0006_alter_option_question_alter_option_text_and_more.py deleted file mode 100644 index c608f65..0000000 --- a/api/masteriqapp/migrations/0006_alter_option_question_alter_option_text_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.0.2 on 2024-03-18 20:09 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('masteriqapp', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='option', - name='question', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='masteriqapp.question'), - ), - migrations.AlterField( - model_name='option', - name='text', - field=models.CharField(max_length=1024), - ), - migrations.AlterField( - model_name='question', - name='text', - field=models.CharField(max_length=1024), - ), - ] diff --git a/api/masteriqapp/models/CustomUser.py b/api/masteriqapp/models/CustomUser.py new file mode 100644 index 0000000..c434071 --- /dev/null +++ b/api/masteriqapp/models/CustomUser.py @@ -0,0 +1,7 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.conf import settings + + +class CustomUser(AbstractUser): + iqs = models.ManyToManyField(settings.AUTH_USER_MODEL, through="IQ") \ No newline at end of file diff --git a/api/masteriqapp/models/IQ.py b/api/masteriqapp/models/IQ.py index 4efc70f..da38f30 100644 --- a/api/masteriqapp/models/IQ.py +++ b/api/masteriqapp/models/IQ.py @@ -4,7 +4,7 @@ class IQManager(models.Manager): def get_best_players_of_category(self, category, nb_players=10): - return self.filter(category=category).order_by('iq')[:nb_players] + return self.filter(category=category).order_by('-iq')[:nb_players] def get_all_iq_of_user(self, user): return self.filter(user=user).values("iq", "category") diff --git a/api/masteriqapp/models/__init__.py b/api/masteriqapp/models/__init__.py index 74f6261..c57aaec 100644 --- a/api/masteriqapp/models/__init__.py +++ b/api/masteriqapp/models/__init__.py @@ -2,3 +2,4 @@ from .IQ import IQ from .Question import Question from .Option import Option +from .CustomUser import CustomUser diff --git a/api/masteriqapp/tests/test_models.py b/api/masteriqapp/tests/test_models.py index 32018f0..ec787b4 100644 --- a/api/masteriqapp/tests/test_models.py +++ b/api/masteriqapp/tests/test_models.py @@ -1,3 +1,4 @@ +from django.contrib.auth import get_user_model from django.test import TestCase import django.apps @@ -5,7 +6,6 @@ from masteriqapp.models import IQ from masteriqapp.models import Question from masteriqapp.models import Option -from django.contrib.auth.models import User class ModelTestCases(TestCase): @@ -24,7 +24,7 @@ def test_use_model(self): option_1 = Option.objects.create(text="Yes", is_correct=False, question=question_test) option_2 = Option.objects.create(text="No", is_correct=True, question=question_test) - user_test = User.objects.create_user("test", "test@example.com", "password") + user_test = get_user_model().objects.create_user("test", "test@example.com", "password") iq_test = IQ.objects.create(user=user_test, category=category_test, iq=100) @@ -32,12 +32,12 @@ def test_use_model(self): assert Question.objects.get(id=question_test.id).text == question_test.text assert Option.objects.get(id=option_1.id).text == option_1.text assert len(Option.objects.filter(question=question_test)) == 2 - assert User.objects.get(id=user_test.id).username == user_test.username + assert get_user_model().objects.get(id=user_test.id).username == user_test.username assert len(IQ.objects.filter(user=user_test, category=category_test)) == 1 def test_use_manager(self): category_test = Category.objects.create(name="test_managers") - user_test = User.objects.create_user("test_managers", "test_managers@example.com", "password") + user_test = get_user_model().objects.create_user("test_managers", "test_managers@example.com", "password") iq_test = IQ.objects.create(user=user_test, category=category_test, iq = 102) leaderboard = IQ.objects.get_best_players_of_category(category=category_test) diff --git a/api/masteriqapp/tests/test_routes.py b/api/masteriqapp/tests/test_routes.py index fe41702..c69e57e 100644 --- a/api/masteriqapp/tests/test_routes.py +++ b/api/masteriqapp/tests/test_routes.py @@ -5,6 +5,10 @@ class RouteTestCases(TestCase): def test_route(self): c = Client() + + response = c.post("/api/user/signup/", {"username":"test", "password":"test"}) + assert response.status_code == 201 + response = c.get("/api/category/1/image/") assert response.status_code == 200 diff --git a/api/masteriqapp/views/AuthenticationView.py b/api/masteriqapp/views/AuthenticationView.py index 6bf73ee..536b4aa 100644 --- a/api/masteriqapp/views/AuthenticationView.py +++ b/api/masteriqapp/views/AuthenticationView.py @@ -1,12 +1,17 @@ +from django.apps import apps from rest_framework import viewsets, status from rest_framework.response import Response -from django.contrib.auth import authenticate, login, logout -from django.contrib.auth.models import User +from django.contrib.auth import authenticate, login, logout, get_user_model from rest_framework.decorators import action from rest_framework.permissions import AllowAny +import masteriqapp.models.IQ + +masteriq = apps.get_app_config("masteriqapp") class AuthenticationView(viewsets.ViewSet): + category_model = masteriq.get_model("Category") + iq_model = masteriq.get_model("IQ") @action(detail=False, methods=['POST'], permission_classes=[AllowAny]) def login(self, request): username = request.data.get('username') @@ -27,9 +32,19 @@ def logout(self, request): def register(self, request): username = request.data.get('username') password = request.data.get('password') - if not User.objects.filter(username=username).exists(): - user = User.objects.create_user(username=username, password=password) + if not get_user_model().objects.filter(username=username).exists(): + user = get_user_model().objects.create_user(username=username, password=password) + self.create_iq_objects_for_new_user(user) login(request, user) return Response({'message': 'Register successful'}, status=status.HTTP_201_CREATED) else: return Response({'message': 'Username already exists'}, status=status.HTTP_400_BAD_REQUEST) + + def create_iq_objects_for_new_user(self, user): + categories = self.category_model.objects.all() + for category in categories: + try: + already_existing_entry = self.iq_model.objects.get_iq_of_user_in_category(user=user, category=category) + except masteriqapp.models.IQ.DoesNotExist: + self.iq_model.objects.create(user=user, category=category, iq=100) + diff --git a/api/masteriqapp/views/IQView.py b/api/masteriqapp/views/IQView.py index c977e86..486d1f2 100644 --- a/api/masteriqapp/views/IQView.py +++ b/api/masteriqapp/views/IQView.py @@ -2,20 +2,23 @@ from django.http import HttpResponse from django.http import JsonResponse +from django.utils.decorators import method_decorator from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.generics import get_object_or_404 from django.apps import apps from rest_framework import status from rest_framework.response import Response -from django.contrib.auth.decorators import login_required +from rest_framework.permissions import IsAuthenticated masteriq = apps.get_app_config("masteriqapp") class IQView(viewsets.ViewSet): category_model = masteriq.get_model("Category") + iq_model = masteriq.get_model("IQ") queryset = category_model.objects.all() + permission_classes = (IsAuthenticated,) @action(detail=True, methods=["GET"], url_path="image") def category_image(self, request, pk): @@ -28,19 +31,21 @@ def category_image(self, request, pk): @action(detail=False, methods=["GET"], url_path="iq") def category_with_iq(self, request): - # TODO: RESTRICT TO CONNECTED USER answer_dict = {} for category in self.queryset: + iq = self.iq_model.objects.get_iq_of_user_in_category(user=request.user, category=category) cat_dict = { "category_name": category.name, - "user_iq": 100 # TODO: REPLACE WITH USER IQ + "user_iq": iq } answer_dict[category.id] = cat_dict return Response(status=status.HTTP_200_OK, data=answer_dict) @action(detail=True, methods=["GET"]) def user_iq(self, request, pk): - return Response(status=status.HTTP_200_OK, data={"user_iq": random.randint(1, 200)}) + category = self.category_model.objects.get(pk=pk) + iq = self.iq_model.objects.get_iq_of_user_in_category(user=request.user, category=category) + return Response(status=status.HTTP_200_OK, data={"user_iq": iq}) @action(detail=True, methods=["GET"], url_path="name") def category_name(self, request, pk): diff --git a/api/masteriqapp/views/QuestionView.py b/api/masteriqapp/views/QuestionView.py index ca938b1..d425e93 100644 --- a/api/masteriqapp/views/QuestionView.py +++ b/api/masteriqapp/views/QuestionView.py @@ -6,6 +6,7 @@ from django.apps import apps from rest_framework import status from rest_framework.generics import get_object_or_404 +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from masteriqapp.serializers.OptionSerializer import OptionSerializer @@ -39,10 +40,15 @@ def check_if_text_answer_is_correct(given_answer, right_answer): class QuestionView(viewsets.ViewSet): + WRONG_PENALTY = 5 + TEXT_RIGHT_POINTS = 5 + OPTIONS_RIGHT_POINTS = 3 category_model = masteriq.get_model("Category") question_model = masteriq.get_model("Question") option_model = masteriq.get_model("Option") + iq_model = masteriq.get_model("IQ") queryset = category_model.objects.all() + permission_classes = (IsAuthenticated,) @action(detail=True, methods=["GET"]) def new(self, request, pk): @@ -120,11 +126,17 @@ def answer_text(self, request): question = self.question_model.objects.get(pk=request.session['question']) right_answer = question.options.get(is_correct=True) user_is_correct = check_if_text_answer_is_correct(request.data['answer'], right_answer.text) + iq = self.iq_model.objects.get(user=request.user, category=question.category) + if request.data['answer'].lower() == right_answer.text.lower(): + user_is_correct = True + iq.iq += self.TEXT_RIGHT_POINTS + else: + iq.iq -= self.WRONG_PENALTY + iq.save() data_to_send = {"user_is_correct": user_is_correct, "right_answer": right_answer.text, "answer_sent": request.data['answer']} del request.session['question'] del request.session['options_asked'] - # TODO: add points to user when connexion is implemented return Response(status=status.HTTP_200_OK, data=data_to_send) @action(detail=False, methods=["POST"], url_path="answer_option") @@ -141,13 +153,18 @@ def answer_options(self, request): right_answer = question.options.get(is_correct=True) answer_sent = self.option_model.objects.get(pk=request.data['answer']) user_is_correct = False + iq = self.iq_model.objects.get(user=request.user, category=question.category) if answer_sent.id == right_answer.id: user_is_correct = True + iq.iq += self.OPTIONS_RIGHT_POINTS + else: + iq.iq -= self.WRONG_PENALTY + iq.save() + data_to_send = {"user_is_correct": user_is_correct, "right_answer": right_answer.text, "answer_sent": answer_sent.text} del request.session['question'] del request.session['options_asked'] - # TODO: add points to user when connexion is implemented return Response(status=status.HTTP_200_OK, data=data_to_send) @action(detail=False, methods=["GET"]) diff --git a/api/masteriqapp/views/RankView.py b/api/masteriqapp/views/RankView.py index acead56..063209f 100644 --- a/api/masteriqapp/views/RankView.py +++ b/api/masteriqapp/views/RankView.py @@ -1,8 +1,12 @@ import random from django.apps import apps +from django.contrib.auth import get_user_model +from django.db.models import Window, F, Subquery, Avg +from django.db.models.functions import RowNumber from rest_framework import viewsets from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status @@ -12,43 +16,52 @@ class RankView(viewsets.ViewSet): category_model = masteriq.get_model("Category") question_model = masteriq.get_model("Question") + user_model = get_user_model() + iq_model = masteriq.get_model("IQ") queryset = category_model.objects.all() - + permission_classes = (IsAuthenticated,) @action(detail=True, methods=["GET"]) def leaderboard(self, request, pk): - # TODO: get data from database when users are implemented data_to_send = [] - for i in range(1, 11): + category = self.category_model.objects.get(pk=pk) + best_iqs = self.iq_model.objects.get_best_players_of_category(category=category) + for iq in best_iqs: + user = iq.user data_to_send.append({ - "user_id": random.randint(1, 1000), - "user_name": f"player_number_{i}", - "user_iq": 150 - (i * 10) + "user_id": user.id, + "user_name": user.username, + "user_iq": iq.iq }) return Response(data=data_to_send, status=status.HTTP_200_OK) @action(detail=False, methods=["GET"]) def global_leaderboard(self, request): - # TODO: get data from database when users are implemented + data_with_score = get_user_model().objects.annotate(global_score=Avg('iq__iq')).order_by('-global_score',)[:10] data_to_send = [] - for i in range(1, 11): + for user in data_with_score: data_to_send.append({ - "user_id": random.randint(1, 1000), - "user_name": f"player_number_{i}", - "user_iq": 150 - (i * 10) + "user_id": user.id, + "user_name": user.username, + "user_iq": user.global_score }) return Response(data=data_to_send, status=status.HTTP_200_OK) @action(detail=True, methods=["GET"]) def user(self, request, pk): - # TODO: get data from database when users are implemented - - data_to_send = {"user_rank": random.randint(1, 1000), "user_iq": random.randint(1, 200)} + #TODO: ask how to do better?? filter won't be efficient if more element + category = self.category_model.objects.get(pk=pk) + ranking = self.iq_model.objects.annotate(row_number=Window(expression=RowNumber(), order_by=F('iq').desc())).filter(category=category).values('iq', 'user_id', 'row_number', 'category_id') + user_ranking = list(filter(lambda r: r['user_id'] == request.user.id, ranking)) + iq = self.iq_model.objects.get(user=request.user, category=category) + data_to_send = {"user_rank": user_ranking[0]['row_number'], "user_iq": iq.iq} return Response(data=data_to_send, status=status.HTTP_200_OK) @action(detail=False, methods=["GET"]) def global_user(self, request): - # TODO: get data from database when users are implemented - data_to_send = {"user_rank": random.randint(1, 1000), "user_iq": random.randint(1, 200)} + # TODO: ask how to do better?? filter won't be efficient if more element + data_with_score = get_user_model().objects.annotate(global_score=Avg('iq__iq'), row_number=Window(expression=RowNumber())).order_by('-global_score') + user_ranking = list(filter(lambda r: r.id == request.user.id, data_with_score)) + data_to_send = {"user_rank": user_ranking[0].row_number, "user_iq": user_ranking[0].global_score} return Response(data=data_to_send, status=status.HTTP_200_OK) diff --git a/frontend/src/api_client.js b/frontend/src/api_client.js index 5100641..4868530 100644 --- a/frontend/src/api_client.js +++ b/frontend/src/api_client.js @@ -5,6 +5,24 @@ const API_SERVER = import.meta.env.VITE_API_SERVER; axios.defaults.baseURL = API_SERVER; axios.defaults.withCredentials = true; +function getCookie(name) { + let cookieValue = null; + if (document.cookie && document.cookie !== '') { + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].trim(); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) === (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; +} + +let csrftoken = getCookie('csrftoken'); +axios.defaults.headers.post['X-CSRFToken'] = csrftoken; export default class APIClient { /** @@ -128,6 +146,7 @@ export default * @returns {Object} {user_is_correct: Boolean, right_answer: String, answer_sent: String} */ static async postAnswerText(answer_text) { + csrftoken = getCookie('csrftoken'); const response = await axios.post(`/api/question/answer_text/`, { answer: answer_text, }); @@ -140,6 +159,7 @@ export default * @returns {Object} {user_is_correct: Boolean, right_answer: String, answer_sent: String} */ static async postAnswerOption(option_id) { + csrftoken = getCookie('csrftoken'); const response = await axios.post(`/api/question/answer_option/`, { answer: option_id, }); @@ -153,6 +173,7 @@ export default * @returns {Object} {"message": String} */ static async registerUser(username, password) { + csrftoken = getCookie('csrftoken'); try { const response = await axios.post('/api/user/register/', { username, @@ -171,6 +192,7 @@ export default * @returns {Object} {"message": String} */ static async loginUser(username, password) { + csrftoken = getCookie('csrftoken'); try { const response = await axios.post('/api/user/login/', { username, @@ -189,13 +211,28 @@ export default * @returns {Object} {"text": String, "category": String } */ static async postNewCommunityQuestion(question, options) { - const response = await axios.post(`/api/question/new_community/`, { + csrftoken = getCookie('csrftoken'); + const + response = await axios.post(`/api/question/new_community/`, { question, options, answer: '0' }); + return response.data; + } - return response.data; + /** + * Log out an existing user + * @returns {Object} The response data from the API + */ + static async logOutUser() { + csrftoken = getCookie('csrftoken'); + try { + const response = await axios.post('/api/user/logout/',); + return response.data; + } catch (error) { + throw new Error('Error logging out: ' + error.message); + } } }