Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 22, 2025

Added Battlle Arena (3v3 , 1v1)

…routes, and UI components

Co-authored-by: Hackeries <123194124+Hackeries@users.noreply.github.com>
Co-authored-by: Hackeries <123194124+Hackeries@users.noreply.github.com>
Co-authored-by: Hackeries <123194124+Hackeries@users.noreply.github.com>
Co-authored-by: Hackeries <123194124+Hackeries@users.noreply.github.com>
Co-authored-by: Hackeries <123194124+Hackeries@users.noreply.github.com>
Co-authored-by: Hackeries <123194124+Hackeries@users.noreply.github.com>
…nknown'

Co-authored-by: Hackeries <123194124+Hackeries@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Battle Arena flagship feature for AlgoRise Add Battle Arena: ELO-based competitive programming with 1v1/3v3 modes Dec 22, 2025
Copilot AI requested a review from Hackeries December 22, 2025 16:52
@Hackeries Hackeries marked this pull request as ready for review December 22, 2025 16:55
Copilot AI review requested due to automatic review settings December 22, 2025 16:55
@Hackeries
Copy link
Owner

@copilot have you added to the sidebar??

Co-authored-by: Hackeries <123194124+Hackeries@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 22, 2025

@copilot have you added to the sidebar??

Yes! Added Battle Arena to the sidebar navigation in commit df273b7. It's now positioned between "Practice" and "Contests" with a Swords icon (⚔️).

Repository owner deleted a comment from vercel bot Dec 22, 2025
@Hackeries Hackeries merged commit 9e28d00 into main Dec 22, 2025
3 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements Battle Arena, a competitive programming feature that enables real-time 1v1 problem-solving matches with ELO-based rankings, tier progression (Bronze through Master), and a monetization model separating free users (3 daily unranked matches) from Pro users (unlimited ranked matches). The implementation includes a complete database schema with 7 tables, matchmaking APIs, ELO calculation utilities, and responsive UI components.

Key Changes:

  • Database migration adds arena infrastructure with RLS policies, helper functions for tier calculation and access control, and automatic rating initialization for existing users
  • Backend APIs implement ELO-based matchmaking with dynamic ranges, leaderboard ranking, and match history tracking
  • Frontend components provide lobby, leaderboard, and result screens with tier badges, streak tracking, and Pro/Free tier differentiation

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 27 comments.

Show a summary per file
File Description
types/arena.ts Type definitions for all arena entities, game state, and ELO system with tier constants and utility functions
supabase/migrations/003_battle_arena.sql Complete database schema with 7 tables, RLS policies, indexes, helper functions, and initial data seeding
supabase/migrations/README_ARENA.md Migration guide with setup instructions, verification queries, and troubleshooting steps
lib/arena/elo.ts ELO rating calculation utilities with K-factor adaptation, tier progression logic, and matchmaking range determination
docs/BATTLE_ARENA.md Technical documentation covering architecture, API endpoints, components, and game mechanics
components/arena/result-screen.tsx Post-match results UI showing ELO changes, tier promotions, titles earned, and problem breakdowns
components/arena/index.ts Component exports for arena features
components/arena/arena-lobby.tsx Main lobby UI for match type/mode selection, player stats display, and Pro/Free tier messaging
components/arena/arena-leaderboard.tsx Leaderboard display with top 50 rankings, tier badges, and win rate statistics
app/arena/page.tsx Arena page entry point with authentication, subscription checking, and rating initialization
app/api/arena/matchmaking/route.ts Matchmaking endpoint with ELO-based pairing, problem selection, and match creation logic
app/api/arena/leaderboard/route.ts Leaderboard API with flexible profile field mapping and ELO-based ranking
app/api/arena/history/route.ts Match history endpoint returning user's past matches with pagination
Comments suppressed due to low confidence (1)

supabase/migrations/003_battle_arena.sql:309

  • The RLS policy 'arena_ratings_update_system' allows any authenticated user to update any arena rating record. The policy uses USING (true) which means there are no restrictions on who can update ratings. This is a significant security vulnerability as users could manipulate their own or others' ELO ratings, match statistics, and win streaks. The comment indicates this is for "System/Edge Functions" but RLS policies don't distinguish between regular users and edge functions when both use authenticated connections. Consider using a service role key for system operations or implementing a more restrictive policy.
CREATE POLICY "arena_ratings_update_system"
    ON arena_ratings FOR UPDATE
    USING (true); -- System/Edge Functions update ratings

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +235 to +255
CREATE OR REPLACE FUNCTION get_tier_from_elo(elo INTEGER)
RETURNS VARCHAR(20)
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
IF elo < 1000 THEN
RETURN 'bronze';
ELSIF elo < 1200 THEN
RETURN 'silver';
ELSIF elo < 1400 THEN
RETURN 'gold';
ELSIF elo < 1600 THEN
RETURN 'platinum';
ELSIF elo < 1800 THEN
RETURN 'diamond';
ELSE
RETURN 'master';
END IF;
END;
$$;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect usage of IMMUTABLE function classification. The get_tier_from_elo function is marked as IMMUTABLE, which tells PostgreSQL that the function always returns the same result for the same input and has no side effects. While this is technically true for this function, it's worth noting that if the tier thresholds ever need to be changed (e.g., rebalancing tiers), this function would need to be recreated or altered. Consider using STABLE instead if there's any chance the tier boundaries might be configuration-driven in the future, though IMMUTABLE is acceptable for hardcoded logic.

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +112
// Try to find an existing waiting match
const { data: waitingMatches, error: matchError } = await supabase
.from('arena_matches')
.select('*, arena_ratings!inner(elo_1v1)')
.eq('match_type', '1v1')
.eq('mode', mode)
.eq('state', 'waiting')
.is('player2_id', null)
.neq('player1_id', user.id)
.gte('arena_ratings.elo_1v1', minElo)
.lte('arena_ratings.elo_1v1', maxElo)
.limit(1);

if (!matchError && waitingMatches && waitingMatches.length > 0) {
const match = waitingMatches[0];

Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type safety issue with arena_ratings join syntax. The query on line 100 uses arena_ratings!inner(elo_1v1) which is Supabase PostgREST syntax for joining tables. However, there's no explicit foreign key defined between arena_matches.player1_id and arena_ratings.user_id in the schema. While the relationship exists logically, Supabase's automatic foreign key detection might not work without an explicit FOREIGN KEY constraint. This could cause the query to fail at runtime. Verify that this join syntax works, or restructure to use explicit filtering.

Suggested change
// Try to find an existing waiting match
const { data: waitingMatches, error: matchError } = await supabase
.from('arena_matches')
.select('*, arena_ratings!inner(elo_1v1)')
.eq('match_type', '1v1')
.eq('mode', mode)
.eq('state', 'waiting')
.is('player2_id', null)
.neq('player1_id', user.id)
.gte('arena_ratings.elo_1v1', minElo)
.lte('arena_ratings.elo_1v1', maxElo)
.limit(1);
if (!matchError && waitingMatches && waitingMatches.length > 0) {
const match = waitingMatches[0];
// Try to find an existing waiting match (without relying on implicit joins)
const { data: waitingMatchesRaw, error: matchError } = await supabase
.from('arena_matches')
.select('*')
.eq('match_type', '1v1')
.eq('mode', mode)
.eq('state', 'waiting')
.is('player2_id', null)
.neq('player1_id', user.id)
.limit(10);
let match = null as (typeof waitingMatchesRaw extends Array<infer T> ? T : null) | null;
if (!matchError && waitingMatchesRaw && waitingMatchesRaw.length > 0) {
// Explicitly filter by opponent ELO range using arena_ratings
for (const candidate of waitingMatchesRaw) {
// Ensure we have a player1_id to look up
if (!candidate || !('player1_id' in candidate) || !candidate.player1_id) {
continue;
}
const { data: rating, error: ratingError } = await supabase
.from('arena_ratings')
.select('elo_1v1')
.eq('user_id', candidate.player1_id)
.single();
if (!ratingError && rating && rating.elo_1v1 >= minElo && rating.elo_1v1 <= maxElo) {
match = candidate;
break;
}
}
}
if (match) {

Copilot uses AI. Check for mistakes.
Comment on lines +331 to +335
WITH CHECK (true); -- Matchmaking system creates matches

CREATE POLICY "arena_matches_update_system"
ON arena_matches FOR UPDATE
USING (true); -- System updates match state
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar security issue with arena_matches RLS policies. The 'arena_matches_update_system' policy allows any authenticated user to update any match record using USING (true). This means users could potentially manipulate match states, winner IDs, ELO changes, and final scores. Since matches affect rating calculations and competitive integrity, this is a critical security vulnerability. System operations should use service role keys or implement policies that restrict updates based on specific conditions or roles.

Suggested change
WITH CHECK (true); -- Matchmaking system creates matches
CREATE POLICY "arena_matches_update_system"
ON arena_matches FOR UPDATE
USING (true); -- System updates match state
WITH CHECK (auth.role() = 'service_role'); -- Matchmaking system creates matches
CREATE POLICY "arena_matches_update_system"
ON arena_matches FOR UPDATE
USING (auth.role() = 'service_role'); -- System updates match state

Copilot uses AI. Check for mistakes.
Comment on lines +354 to +356
CREATE POLICY "arena_players_update_own"
ON arena_players FOR UPDATE
USING (user_id = auth.uid());
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The arena_players update policy allows users to update their own player records, but this could be exploited. A user could modify their own problems_solved, score, solve_times, or even activity_status to gain unfair advantages. For example, they could artificially inflate their score or mark problems as solved without actually solving them. Player state updates should be restricted to specific allowed fields (like last_seen_at) or should only be performable by backend systems using elevated privileges.

Suggested change
CREATE POLICY "arena_players_update_own"
ON arena_players FOR UPDATE
USING (user_id = auth.uid());
-- Arena Players updates are restricted to backend systems using elevated privileges
-- (service-role keys bypass RLS). End-users cannot directly update arena_players rows.
CREATE POLICY "arena_players_update_system"
ON arena_players FOR UPDATE
USING (false)
WITH CHECK (false);

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +149
current_problem_id VARCHAR(100), -- Currently working on
problems_solved JSONB DEFAULT '[]', -- Array of solved problem IDs
problems_attempted JSONB DEFAULT '[]', -- Array of attempted problem IDs

-- Fog of Progress status
activity_status VARCHAR(20) DEFAULT 'idle', -- idle, attempting, close, solved
last_activity_at TIMESTAMPTZ DEFAULT NOW(),

-- Locks (for 3v3)
locked_problem_id VARCHAR(100), -- Problem this player has locked
locked_at TIMESTAMPTZ,
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type inconsistency in problem IDs. The arena_players table uses VARCHAR(100) for current_problem_id and locked_problem_id, while the problems table likely uses UUID for problem IDs (standard practice). The match creation code uses problemIds: await selectMatchProblems(...) which returns string[], and the selectMatchProblems function maps p.id from the problems table. If problem IDs are UUIDs in the database but are being stored as VARCHAR, this creates a type mismatch. Consider using UUID type consistently or verifying that problem IDs are actually strings in the problems table schema.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,287 @@
'use client';

import { useState, useEffect } from 'react';
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import useEffect.

Suggested change
import { useState, useEffect } from 'react';
import { useState } from 'react';

Copilot uses AI. Check for mistakes.
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import Progress.

Suggested change
import { Progress } from '@/components/ui/progress';

Copilot uses AI. Check for mistakes.

CREATE POLICY "arena_ratings_update_system"
ON arena_ratings FOR UPDATE
USING (true); -- System/Edge Functions update ratings
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RLS policy arena_ratings_update_system uses USING (true), which means any role with UPDATE privileges (including regular authenticated clients in a typical Supabase setup) can modify any row in arena_ratings. An attacker can directly call UPDATE arena_ratings from the client to arbitrarily change their own or other users’ ELO, tiers, streaks, and titles, completely undermining leaderboard integrity. Restrict this update policy to a privileged context (for example, service-role or a dedicated claim) or to the owning user_id, and route all rating changes through trusted server-side logic instead of allowing arbitrary client writes.

Suggested change
USING (true); -- System/Edge Functions update ratings
USING (auth.role() = 'service_role') -- Restrict updates to service-role context
WITH CHECK (auth.role() = 'service_role');

Copilot uses AI. Check for mistakes.
Comment on lines +318 to +335
CREATE POLICY "arena_teams_insert_system"
ON arena_teams FOR INSERT
WITH CHECK (true); -- Edge Functions create teams

-- Arena Matches: Public read for match discovery, system write
ALTER TABLE arena_matches ENABLE ROW LEVEL SECURITY;

CREATE POLICY "arena_matches_select_all"
ON arena_matches FOR SELECT
USING (true);

CREATE POLICY "arena_matches_insert_system"
ON arena_matches FOR INSERT
WITH CHECK (true); -- Matchmaking system creates matches

CREATE POLICY "arena_matches_update_system"
ON arena_matches FOR UPDATE
USING (true); -- System updates match state
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The arena_teams_insert_system, arena_matches_insert_system, and arena_matches_update_system policies all use WITH CHECK (true) / USING (true), so any authenticated client with basic table privileges can create or modify teams and matches directly. This lets an attacker forge matches (e.g., set themselves as winner, change mode, problem_ids, or state) and manipulate team compositions outside of the intended matchmaking Edge Functions, corrupting the authoritative game state. Tighten these policies so that only trusted backend roles (e.g., auth.role() = 'service_role' or a dedicated system claim) can insert/update these rows, preventing arbitrary client-side tampering with core arena match data.

Copilot uses AI. Check for mistakes.
Comment on lines +350 to +384
CREATE POLICY "arena_players_insert_system"
ON arena_players FOR INSERT
WITH CHECK (true);

CREATE POLICY "arena_players_update_own"
ON arena_players FOR UPDATE
USING (user_id = auth.uid());

-- Arena Events: Can view events for matches they're in
ALTER TABLE arena_events ENABLE ROW LEVEL SECURITY;

CREATE POLICY "arena_events_select_own_match"
ON arena_events FOR SELECT
USING (
EXISTS (
SELECT 1 FROM arena_players ap
WHERE ap.match_id = arena_events.match_id
AND ap.user_id = auth.uid()
)
);

CREATE POLICY "arena_events_insert_system"
ON arena_events FOR INSERT
WITH CHECK (true);

-- Arena Match History: Users can only see their own history
ALTER TABLE arena_match_history ENABLE ROW LEVEL SECURITY;

CREATE POLICY "arena_match_history_select_own"
ON arena_match_history FOR SELECT
USING (auth.uid() = user_id);

CREATE POLICY "arena_match_history_insert_system"
ON arena_match_history FOR INSERT
WITH CHECK (true);
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because arena_players_insert_system, arena_events_insert_system, and arena_match_history_insert_system all use WITH CHECK (true), any authenticated client can insert rows into arena_players, arena_events, and arena_match_history without restriction. Combined with the arena_players_select_own_match / arena_events_select_own_match policies, a malicious user can insert themselves into any existing match_id to gain read access to all player and event records for that match, and can also fabricate match-history rows for arbitrary user_ids, breaking both privacy and integrity of arena data. Restrict these insert policies to a privileged role or tie them to auth.uid() as appropriate so that only trusted server-side code can create authoritative player, event, and history records for a match.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants