diff --git a/api/leaderboard/celery.py b/api/leaderboard/celery.py index ea8bebe..a433070 100644 --- a/api/leaderboard/celery.py +++ b/api/leaderboard/celery.py @@ -434,13 +434,14 @@ def unified_score_snapshot(self): if df.empty: return for _, row in df.iterrows(): - username = row.get("username") - if not username: + user_id = row.get("user_id") + if not user_id: continue UnifiedScoreHistory.objects.update_or_create( - username=username, + user_id=user_id, date=today, defaults={ + "username": row.get("username"), "total_score": float(row.get("total_score", 0)), "github_score": float(row.get("github_score", 0)), "cf_score": float(row.get("cf_score", 0)), diff --git a/api/leaderboard/migrations/0019_codechefuser_calendar_data.py b/api/leaderboard/migrations/0019_codechefuser_calendar_data.py new file mode 100644 index 0000000..f28fa5d --- /dev/null +++ b/api/leaderboard/migrations/0019_codechefuser_calendar_data.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.5 on 2026-03-21 09:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("leaderboard", "0002_alter_codeforcesuser_last_activity_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="codechefuser", + name="calendar_data", + field=models.TextField(blank=True, default="[]"), + ), + ] diff --git a/api/leaderboard/migrations/0020_organization_organizationmember.py b/api/leaderboard/migrations/0020_organization_organizationmember.py new file mode 100644 index 0000000..a9ca8a7 --- /dev/null +++ b/api/leaderboard/migrations/0020_organization_organizationmember.py @@ -0,0 +1,76 @@ +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + +class Migration(migrations.Migration): + + dependencies = [ + ("leaderboard", "0019_codechefuser_calendar_data"), + ] + + operations = [ + migrations.CreateModel( + name="Organization", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=128)), + ("description", models.TextField(blank=True, null=True)), + ("is_private", models.BooleanField(default=True)), + ( + "join_code", + models.CharField(blank=True, max_length=10, null=True, unique=True), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "admin", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="administered_organizations", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="OrganizationMember", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("joined_at", models.DateTimeField(auto_now_add=True)), + ( + "organization", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="memberships", + to="leaderboard.organization", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="organization_memberships", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "unique_together": {("organization", "user")}, + }, + ), + ] diff --git a/api/leaderboard/migrations/0021_leetcodeuser_calendar_data_usernames_ac_uname_and_more.py b/api/leaderboard/migrations/0021_leetcodeuser_calendar_data_usernames_ac_uname_and_more.py new file mode 100644 index 0000000..6a82a1a --- /dev/null +++ b/api/leaderboard/migrations/0021_leetcodeuser_calendar_data_usernames_ac_uname_and_more.py @@ -0,0 +1,118 @@ +# Generated by Django 5.1.5 on 2026-03-24 08:58 + +import django.db.models.deletion +import leaderboard.models +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("leaderboard", "0020_organization_organizationmember"), + ] + + operations = [ + migrations.AddField( + model_name="leetcodeuser", + name="calendar_data", + field=models.TextField(blank=True, default="{}"), + ), + migrations.AddField( + model_name="usernames", + name="ac_uname", + field=models.CharField(default="", max_length=64), + ), + migrations.AddField( + model_name="usernames", + name="bio", + field=models.TextField(blank=True, default="", max_length=500), + ), + migrations.AddField( + model_name="usernames", + name="location", + field=models.CharField(blank=True, default="", max_length=128), + ), + migrations.AddField( + model_name="usernames", + name="occupation", + field=models.CharField(blank=True, default="", max_length=128), + ), + migrations.AddField( + model_name="usernames", + name="organization", + field=models.CharField(blank=True, default="", max_length=128), + ), + migrations.AlterField( + model_name="codeforcesuser", + name="last_activity", + field=models.BigIntegerField( + default=leaderboard.models.get_default_cf_last_activity + ), + ), + migrations.CreateModel( + name="Achievement", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("slug", models.CharField(max_length=64)), + ("tier", models.CharField(max_length=64)), + ("unlocked_at", models.DateTimeField(auto_now_add=True)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "unique_together": {("user", "slug", "tier")}, + }, + ), + migrations.CreateModel( + name="PostVote", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "vote_type", + models.CharField( + choices=[("like", "Like"), ("dislike", "Dislike")], max_length=7 + ), + ), + ( + "post", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="votes", + to="leaderboard.discussionpost", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "unique_together": {("user", "post")}, + }, + ), + ] diff --git a/api/leaderboard/migrations/0022_alter_organization_join_code.py b/api/leaderboard/migrations/0022_alter_organization_join_code.py new file mode 100644 index 0000000..b8b193d --- /dev/null +++ b/api/leaderboard/migrations/0022_alter_organization_join_code.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.5 on 2026-03-28 11:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("leaderboard", "0021_leetcodeuser_calendar_data_usernames_ac_uname_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="organization", + name="join_code", + field=models.CharField(blank=True, max_length=10, null=True, unique=True), + ), + ] diff --git a/api/leaderboard/migrations/0023_merge_20260329_1109.py b/api/leaderboard/migrations/0023_merge_20260329_1109.py new file mode 100644 index 0000000..2881d6e --- /dev/null +++ b/api/leaderboard/migrations/0023_merge_20260329_1109.py @@ -0,0 +1,13 @@ +# Generated by Django 5.1.5 on 2026-03-29 11:09 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("leaderboard", "0019_codechefuser_calendar_data"), + ("leaderboard", "0022_alter_organization_join_code"), + ] + + operations = [] diff --git a/api/leaderboard/migrations/0024_alter_codeforcesuser_last_activity.py b/api/leaderboard/migrations/0024_alter_codeforcesuser_last_activity.py new file mode 100644 index 0000000..f92863f --- /dev/null +++ b/api/leaderboard/migrations/0024_alter_codeforcesuser_last_activity.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.5 on 2026-03-29 11:10 + +import leaderboard.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("leaderboard", "0023_merge_20260329_1109"), + ] + + operations = [ + migrations.AlterField( + model_name="codeforcesuser", + name="last_activity", + field=models.BigIntegerField( + default=leaderboard.models.get_current_timestamp + ), + ), + ] diff --git a/api/leaderboard/migrations/0025_alter_unifiedscorehistory_unique_together_and_more.py b/api/leaderboard/migrations/0025_alter_unifiedscorehistory_unique_together_and_more.py new file mode 100644 index 0000000..209316e --- /dev/null +++ b/api/leaderboard/migrations/0025_alter_unifiedscorehistory_unique_together_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 5.1.5 on 2026-03-29 12:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("leaderboard", "0024_alter_codeforcesuser_last_activity"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="unifiedscorehistory", + unique_together=set(), + ), + migrations.AddField( + model_name="unifiedscorehistory", + name="user_id", + field=models.IntegerField(db_index=True, default=0), + ), + migrations.AlterUniqueTogether( + name="unifiedscorehistory", + unique_together={("user_id", "date")}, + ), + ] diff --git a/api/leaderboard/models.py b/api/leaderboard/models.py index ab53e37..1e98e9f 100644 --- a/api/leaderboard/models.py +++ b/api/leaderboard/models.py @@ -275,6 +275,7 @@ class UnifiedScoreHistory(models.Model): per-platform breakdown. Written by a Celery beat task every day. Used by the Unified trend analysis heatmap and line chart. """ + user_id = models.IntegerField(db_index=True, default=0) username = models.CharField(max_length=64, db_index=True) date = models.DateField(db_index=True) @@ -291,7 +292,7 @@ class UnifiedScoreHistory(models.Model): class Meta: # one record per user per day - unique_together = ("username", "date") + unique_together = ("user_id", "date") ordering = ["date"] def __str__(self): diff --git a/api/leaderboard/views.py b/api/leaderboard/views.py index 4b7f2a2..a8de670 100644 --- a/api/leaderboard/views.py +++ b/api/leaderboard/views.py @@ -55,7 +55,7 @@ MAX_DATE_TIMESTAMP = datetime.now().timestamp() import requests -from django.db import connection +from django.db import connection, transaction from django.db.utils import OperationalError from rest_framework import generics, mixins, status from rest_framework.response import Response @@ -937,6 +937,7 @@ def get_queryset(self): # User sees organizations they are members of return Organization.objects.filter(memberships__user=self.request.user) + @transaction.atomic def perform_create(self, serializer): # Admin is the current user organization = serializer.save(admin=self.request.user) diff --git a/app/src/App.css b/app/src/App.css index ef65cf3..dc075dd 100644 --- a/app/src/App.css +++ b/app/src/App.css @@ -9,10 +9,10 @@ html, body { } .App { - overflow-x: hidden; - max-width: 100%; - height: 100vh; - overflow-y: auto; + display: flex; + flex-direction: column; + min-height: 100vh; + width: 100%; } @media (prefers-reduced-motion: no-preference) { diff --git a/app/src/App.jsx b/app/src/App.jsx index 6168eb7..fb0d4ce 100644 --- a/app/src/App.jsx +++ b/app/src/App.jsx @@ -33,10 +33,6 @@ import OrganizationLeaderboard from "./components/OrganizationLeaderboard"; import { UnifiedLeaderboard } from "./components/UnifiedLeaderboard"; import { TrendAnalysis } from "@/components/TrendAnalysis"; -import PublicRoute from "./Context/PublicRoute"; -import ContestCalendar from "./components/ContestCalendar"; -import Blogs from "./components/Blogs.jsx"; -import Achievements from "./components/Achievements.jsx"; const BACKEND = import.meta.env.VITE_BACKEND; const fetchListSafely = async (endpoint, setData) => { @@ -82,9 +78,9 @@ function App() { - -
- + + +
} /> @@ -207,7 +203,6 @@ function App() { } /> - {/* ADD YOUR NEW ROUTE HERE */} } /> - {/* } /> */} - - - - } - /> } /> - -
-
+ + +