From fa53d74295172210a6df49f3d140bdc7c49b46ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gremaud?= Date: Sun, 10 May 2026 02:11:30 +0200 Subject: [PATCH] noita leaderboard + view + management commands --- polylan_submitter/noita/api.py | 9 +- .../noita/management/__init__.py | 0 .../noita/management/commands/__init__.py | 0 .../commands/load_objectiv_points.py | 62 ++++++++++++++ polylan_submitter/noita/schemas.py | 1 + polylan_submitter/src/Noita.vue | 82 ++++++++++++++++++- 6 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 polylan_submitter/noita/management/__init__.py create mode 100644 polylan_submitter/noita/management/commands/__init__.py create mode 100644 polylan_submitter/noita/management/commands/load_objectiv_points.py diff --git a/polylan_submitter/noita/api.py b/polylan_submitter/noita/api.py index 854c242..a6e0cb9 100644 --- a/polylan_submitter/noita/api.py +++ b/polylan_submitter/noita/api.py @@ -5,6 +5,7 @@ from django.db.models import ( Case, When, Sum, + Count, IntegerField, Subquery, OuterRef, @@ -154,7 +155,6 @@ def get_leaderboard(request: HttpRequest): # Get unique users and their scores, then apply ranking leaderboard = ( - # User.objects.filter(objectiv_set__isnull=False) User.objects.filter(objectiv__isnull=False) .distinct() .annotate( @@ -163,13 +163,14 @@ def get_leaderboard(request: HttpRequest): output_field=IntegerField(), ) ) + .annotate(objectives_count=Count("objectiv", distinct=True)) .annotate( rank=Window( expression=Rank(), order_by=F("total_score").desc(), ) ) - .values("rank", "username", "total_score") + .values("rank", "username", "total_score", "objectives_count") .order_by("rank") ) @@ -179,13 +180,14 @@ def get_leaderboard(request: HttpRequest): "rank": entry["rank"], "username": entry["username"], "total_score": entry["total_score"] or 0, + "objectives_count": entry["objectives_count"], } for entry in leaderboard ] } -@router.post("submit", response=NoitaSubmissionOut) +@router.post("submit", response={200: NoitaSubmissionOut, 400: dict}) def submit_log_file(request: HttpRequest, file: UploadedFile = File(...)): """ Submit a Noita run file (log file, screenshot, or video). @@ -200,6 +202,7 @@ def submit_log_file(request: HttpRequest, file: UploadedFile = File(...)): # Validate file type allowed_types = [ "text/plain", + "text/x-log", "image/jpeg", "image/jpg", "image/png", diff --git a/polylan_submitter/noita/management/__init__.py b/polylan_submitter/noita/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/polylan_submitter/noita/management/commands/__init__.py b/polylan_submitter/noita/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/polylan_submitter/noita/management/commands/load_objectiv_points.py b/polylan_submitter/noita/management/commands/load_objectiv_points.py new file mode 100644 index 0000000..085cf28 --- /dev/null +++ b/polylan_submitter/noita/management/commands/load_objectiv_points.py @@ -0,0 +1,62 @@ +from django.core.management.base import BaseCommand +from noita.models import ObjectivPoint +from noita.services.decode import POINTS + + +class Command(BaseCommand): + help = "Load ObjectivPoints from the POINTS dictionary in services.decode" + + def add_arguments(self, parser): + parser.add_argument( + "--clear", + action="store_true", + help="Clear all existing ObjectivPoints before loading", + ) + + def handle(self, *args, **options): + if options["clear"]: + ObjectivPoint.objects.all().delete() + self.stdout.write(self.style.SUCCESS("Cleared existing ObjectivPoints")) + + created_count = 0 + updated_count = 0 + + for objectiv_id, point_value in POINTS.items(): + # Skip special entries + if objectiv_id in {"-", "DEBUG"}: + continue + + # Get display string from objectiv_id (convert to title case) + display_string = objectiv_id.replace("_", " ").title() + + # Create or update ObjectivPoint + obj, created = ObjectivPoint.objects.get_or_create( + objectiv_id=objectiv_id, + defaults={ + "display_string": display_string, + "point": point_value, + "max_count": 1, # Default max_count is 1 + }, + ) + + if created: + created_count += 1 + self.stdout.write(f"✓ Created: {objectiv_id} - {point_value} points") + else: + # Update if points changed + if obj.point != point_value or obj.display_string != display_string: + obj.point = point_value + obj.display_string = display_string + obj.save() + updated_count += 1 + self.stdout.write( + self.style.WARNING( + f"↻ Updated: {objectiv_id} - {point_value} points" + ) + ) + + self.stdout.write(self.style.SUCCESS(f"\nCreated: {created_count}")) + self.stdout.write(self.style.SUCCESS(f"Updated: {updated_count}")) + self.stdout.write( + self.style.SUCCESS(f"Total ObjectivPoints: {ObjectivPoint.objects.count()}") + ) diff --git a/polylan_submitter/noita/schemas.py b/polylan_submitter/noita/schemas.py index f00bb6c..1b1411c 100644 --- a/polylan_submitter/noita/schemas.py +++ b/polylan_submitter/noita/schemas.py @@ -37,6 +37,7 @@ class LeaderboardEntryOut(Schema): rank: int username: str total_score: int + objectives_count: int class LeaderboardOut(Schema): diff --git a/polylan_submitter/src/Noita.vue b/polylan_submitter/src/Noita.vue index 6e5142a..0ebf186 100644 --- a/polylan_submitter/src/Noita.vue +++ b/polylan_submitter/src/Noita.vue @@ -19,6 +19,8 @@ const isDragover = ref(false); const objectives = ref([]); const isLoadingObjectives = ref(false); const isLoadingLeaderboard = ref(false); +const leaderboard = ref([]); +const isLeaderboardModalOpen = ref(false); const handleFileUpload = (event: Event) => { const input = event.target as HTMLInputElement; @@ -127,10 +129,11 @@ const fetchLeaderboard = async () => { const response = await fetch("/api/noita/leaderboard"); if (!response.ok) throw new Error("Failed to fetch leaderboard"); - const leaderboard = await response.json(); + const data = await response.json(); + leaderboard.value = data.leaderboard; // Find current user's rank - const userRank = leaderboard.leaderboard.find( + const userRank = leaderboard.value.find( (entry: any) => entry.username === userInfo.value.username ); @@ -230,8 +233,8 @@ onMounted(() => { - @@ -341,5 +344,76 @@ onMounted(() => { + + +