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() {