From 0da60449137fb9eaa0123ffb0cf207d4549293c4 Mon Sep 17 00:00:00 2001 From: harsh6921 Date: Mon, 22 Sep 2025 19:10:18 +0530 Subject: [PATCH] Add purge queue API method and integrate frontend UI components --- apps/challenges/tests/test_queue_purge.py | 56 +++++++++++++++++++ apps/challenges/urls.py | 6 ++ apps/challenges/views.py | 24 ++++++++ .../challengesubmissions.component.html | 30 ++++++++++ .../src/app/services/challenge.service.ts | 13 +++++ 5 files changed, 129 insertions(+) create mode 100644 apps/challenges/tests/test_queue_purge.py diff --git a/apps/challenges/tests/test_queue_purge.py b/apps/challenges/tests/test_queue_purge.py new file mode 100644 index 0000000000..6c89496b6c --- /dev/null +++ b/apps/challenges/tests/test_queue_purge.py @@ -0,0 +1,56 @@ +from django.contrib.auth.models import User +from rest_framework import status +from rest_framework.test import APITestCase +from django.urls import reverse +from challenges.models import Challenge, ChallengePhase +from hosts.models import ChallengeHostTeam, ChallengeHost +from unittest.mock import patch + +class QueuePurgeAPITestCase(APITestCase): + + def setUp(self): + # Create a user and challenge host team + self.user = User.objects.create_user(username='hostuser', password='testpass') + self.host_team = ChallengeHostTeam.objects.create(team_name='Host Team', created_by=self.user) + self.challenge = Challenge.objects.create(title='Test Challenge', creator=self.host_team, published=True) + self.challenge_phase = ChallengePhase.objects.create(name='Phase 1', challenge=self.challenge) + + # Make host relationship + self.challenge_host = ChallengeHost.objects.create(user=self.user, team_name=self.host_team, status=ChallengeHost.ACCEPTED) + self.url = reverse('purge_challenge_queue', kwargs={'challenge_pk': self.challenge.pk}) + + def test_purge_unauthenticated(self): + response = self.client.post(self.url, {'scope': 'all', 'dry_run': False}) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_purge_forbidden_for_non_host(self): + non_host_user = User.objects.create_user(username='nonhost', password='pass') + self.client.force_authenticate(user=non_host_user) + response = self.client.post(self.url, {'scope': 'all', 'dry_run': False}) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + @patch('challenges.services.QueuePurgeService.purge_queue') + def test_purge_success(self, mock_purge): + mock_purge.return_value = { + 'purged': {'pending': 5, 'running': 2, 'total': 7}, + 'skipped': 0, + 'took_ms': 123, + 'notes': 'Purged successfully', + 'queues': ['submission_1_1'] + } + self.client.force_authenticate(user=self.user) + response = self.client.post(self.url, {'scope': 'all', 'dry_run': False}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('purged', response.data) + self.assertEqual(response.data['purged']['total'], 7) + + def test_purge_invalid_scope(self): + self.client.force_authenticate(user=self.user) + response = self.client.post(self.url, {'scope': 'invalid-scope', 'dry_run': False}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_purge_challenge_not_found(self): + self.client.force_authenticate(user=self.user) + url = reverse('purge_challenge_queue', kwargs={'challenge_pk': 9999}) + response = self.client.post(url, {'scope': 'all', 'dry_run': False}) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/apps/challenges/urls.py b/apps/challenges/urls.py index 3139d5381c..6b0e005d8e 100644 --- a/apps/challenges/urls.py +++ b/apps/challenges/urls.py @@ -1,4 +1,6 @@ from django.conf.urls import url +from django.urls import path +from .views import purge_challenge_queue from . import views @@ -314,6 +316,10 @@ views.modify_leaderboard_data, name="modify_leaderboard_data", ), + url( + r"^challenge/(?P[0-9]+)/queue/purge/$", + views.purge_challenge_queue, + name="purge_challenge_queue"), ] app_name = "challenges" diff --git a/apps/challenges/views.py b/apps/challenges/views.py index 87409330b9..9ff7eebf26 100644 --- a/apps/challenges/views.py +++ b/apps/challenges/views.py @@ -11,6 +11,14 @@ from datetime import datetime from os.path import basename, isfile, join +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from challenges.models import Challenge +from hosts.utils import is_user_a_host_of_challenge +from .services import QueuePurgeService +from rest_framework import status + import pytz import requests import yaml @@ -5043,6 +5051,22 @@ def update_challenge_attributes(request): } return Response(response_data, status=status.HTTP_200_OK) +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def purge_challenge_queue(request, challenge_pk): + if not is_user_a_host_of_challenge(request.user, challenge_pk): + return Response({'error': 'Not authorized.'}, status=status.HTTP_403_FORBIDDEN) + try: + challenge = Challenge.objects.get(pk=challenge_pk) + except Challenge.DoesNotExist: + return Response({'error': 'Challenge not found.'}, status=status.HTTP_404_NOT_FOUND) + scope = request.data.get('scope', 'all') + dry_run = request.data.get('dry_run', False) + purge_service = QueuePurgeService(challenge) + result = purge_service.purge_queue(scope=scope, dry_run=dry_run) + return Response(result) + + @api_view(["PUT"]) @throttle_classes([UserRateThrottle]) diff --git a/frontend_v2/src/app/components/challenge/challengesubmissions/challengesubmissions.component.html b/frontend_v2/src/app/components/challenge/challengesubmissions/challengesubmissions.component.html index 32169fd3c2..b1e8553c40 100644 --- a/frontend_v2/src/app/components/challenge/challengesubmissions/challengesubmissions.component.html +++ b/frontend_v2/src/app/components/challenge/challengesubmissions/challengesubmissions.component.html @@ -66,6 +66,15 @@
My Submissions
+
+
+ +
+
+ My Submissions + + + + + + \ No newline at end of file diff --git a/frontend_v2/src/app/services/challenge.service.ts b/frontend_v2/src/app/services/challenge.service.ts index e5848491d2..f24574d8d6 100644 --- a/frontend_v2/src/app/services/challenge.service.ts +++ b/frontend_v2/src/app/services/challenge.service.ts @@ -393,4 +393,17 @@ export class ChallengeService { const API_PATH = this.endpointsService.challengeCreateURL(hostTeam); return this.apiService.postFileUrl(API_PATH, formData); } + + /** + * Purge submissions queue for a challenge. + * @param challengeId - The challenge ID + * @param body - {scope: string, dry_run: boolean} + * @returns Observable + */ + purgeQueue(challengeId: number, body: { scope: string; dry_run: boolean }) { + const apiPath = `/challenges/${challengeId}/queue/purge/`; + // Replace this.apiService.postUrl with your actual API POST call method + return this.apiService.postUrl(apiPath, JSON.stringify(body)); + } + }