Skip to content

Add reverse sort to log viewer and environment-aware routing - #22

Closed
SidmoGoesBrrr wants to merge 22 commits into
mainfrom
dev
Closed

SidmoGoesBrrr wants to merge 22 commits into
mainfrom
dev

Conversation

@SidmoGoesBrrr

@SidmoGoesBrrr SidmoGoesBrrr commented Nov 17, 2025 •

Copy link
Copy Markdown
Collaborator

Summary

  • Added reverse sort functionality to log viewer (latest logs first by default)
  • Updated admin logs route to serve environment-specific log files
  • Log viewer now serves fastapi-dev.log for dev, fastapi.log for prod

Changes

Log Viewer Improvements

  • Shows latest logs first by default for easier debugging
  • Added toggle button to switch between latest/oldest first sorting
  • Better UX for monitoring server logs in real-time

Environment-Aware Log Routing

  • Admin logs route now checks PORT environment variable
  • Serves correct log file based on deployment environment
  • Prevents confusion between dev and prod logs

Testing

  • Log viewer displays latest logs first by default
  • Toggle button reverses sort order correctly
  • Route serves fastapi-dev.log when PORT=42069
  • Route serves fastapi.log when PORT=6969

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Duel invite links: Generate shareable, time-limited invitations for challenges.
    • Wagered duels: Challenge players with XP wagers and validation rules.
    • Email invitations: Invite LeetCode users not yet on YeetCode.
    • User search: Find users across YeetCode and LeetCode platforms.
    • Learn section: Interactive frontend coding with Monaco editor and live preview.
    • Health check endpoints: Monitor app status and environment details.
  • Bug Fixes & Improvements

    • Fixed XP data type consistency across all operations.
    • Enhanced error handling and logging system.

Manas Rasane and others added 22 commits November 13, 2025 19:38
- Modified workflow to deploy to separate folders (yeetcode-api vs yeetcode-api-dev)
- Updated main.py to support --env flag for environment selection
- Separate ports: prod (6969) and dev (42069)
- Added .env file patterns to .gitignore for security

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Only run APScheduler on production to prevent duplicate daily problems
- Only run duel monitoring and cleanup tasks on production
- Dev instance now serves API only without background jobs

Fixes duplicate daily problem generation issue

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Added startup banner showing environment, port, and feature status
- New /dev/info endpoint with detailed environment and config info
- Enhanced health check endpoint with environment details
- Prints helpful info on startup for developers
Features:
- New Learn UI step with LeetCode-style frontend problems
- Monaco Editor integration for HTML/CSS/JS editing
- Live preview panel with iframe rendering
- 5 starter problems: Center a Div, Interactive Counter, Gradient Card, Todo List, Responsive Navbar
- Problem difficulty levels (Easy/Medium) with hints and solutions
- Split-panel layout: Problem description | Code editor | Live preview
- Navigation button in leaderboard header

Components Added:
- LearnStep: Main container with state management
- ProblemPanel: Problem selector and description
- CodeEditor: Monaco Editor wrapper with syntax highlighting
- LivePreview: Iframe-based live code preview
- frontendProblems.js: Problem database with 5 challenges

Integration:
- Added Learn UI button to LeaderboardHeader
- Integrated step into App.jsx routing
- Maintains YeetCode design system (yellow cards, black borders, 3D buttons)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Root Cause: Background stats update (every 3min) was overwriting user XP

Bug #1: update_user_in_cache() sent entire user object in WAL
- Changed to send only updated fields: {k: user[k] for k in updates.keys()}
- Prevents unintended overwrites of unrelated fields like XP

Bug #2: Background task didn't preserve XP when updating stats
- Now preserves current_xp before updating easy/medium/hard
- Includes XP in update to maintain bonus XP from daily/duels

Race Condition Fixed:
Before: Stats update → writes user with stale XP=0 → XP reset
After: Stats update → only writes easy/medium/hard/xp → XP preserved

Files Modified:
- scripts/fastapi/cache_operations.py: WAL only sends updated fields
- scripts/fastapi/background_tasks.py: Preserves XP in stats updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The deployment workflow checks http://localhost:$PORT/health but
we only had the root (/) endpoint. Added dedicated /health endpoint
that returns status: healthy for deployment verification.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Stops polling immediately after finding a completed submission to
prevent duplicate completion calls every ~5 seconds.

Root cause: After completing daily, fetchDailyProblem() was called
which reset state, causing the completion guard to fail and the
submission to be "completed" again in an infinite loop.

This was causing:
- API requests every ~5 seconds instead of 60 seconds
- Server overload and socket hang ups
- Excessive resource usage

Fix: Call stopDailyPolling() before triggering completion to
ensure polling stops immediately.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Merges from main:
- Fix infinite loop in daily completion checking (stops polling after completion)
- Add lsof-based process killing in deployment workflow
- Add /health endpoint for deployment verification

Resolves merge conflict in App.jsx by keeping dev's superior error handling
(with rollback and notifications) while integrating the daily completion fix.

This stops the API spam that was causing server overload.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Ensures 'xp' field always exists with default value of 0 in all
user data responses (from both cache and database).

Root cause: Cached user objects sometimes missing 'xp' field,
causing frontend to calculate total XP incorrectly (33,100 instead
of 36,225 - missing 3,125 bonus XP).

This fix ensures:
- get_user() always returns xp field (lines 42-43, 50-51)
- get_user_data_endpoint() always returns xp field (lines 100-101, 108-109)
- Frontend XP calculation never missing bonus XP component

Fixes XP fluctuation between 36,525 and 33,100.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixes XP being returned as string "3125" instead of number 3125,
which prevented frontend from adding bonus XP to total.

Changes:
- cache_operations.py: Convert xp to int when incrementing (line 135)
- routes/users.py: Convert xp to int in all user data responses

This ensures frontend calculates correct total XP:
- Before: 33,100 (missing bonus XP string "3125")
- After: 36,225 (33,100 + 3125 bonus XP)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Background task was preserving XP as string 3125 instead of int, causing frontend to lose bonus XP every 3 minutes.

Now converts to int before preserving. Stops XP flickering.
- Log viewer now shows latest logs first by default for easier debugging
- Added toggle button to reverse sort order between latest/oldest first
- Admin logs route now serves correct log file based on environment:
  - fastapi-dev.log for dev environment (PORT=42069)
  - fastapi.log for prod environment (PORT=6969)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 17, 2025 •

Copy link
Copy Markdown

Walkthrough

This PR introduces duel invite links with optional wagering, email-based user invitations, cross-platform user search (YeetCode/LeetCode), a new interactive learn section for frontend problems with a Monaco-based code editor, and infrastructure updates including new health/info endpoints and deployment timestamps across backend files.

Changes

Cohort / File(s) Summary
Dependency Addition
package.json
Adds @monaco-editor/react ^4.7.0 for Monaco editor support.
Duel Invite Link Operations
scripts/fastapi/aws.py
Introduces DuelInviteLinkOperations class with generate_duel_link() and get_duel_link_info() static methods to create, store, and retrieve invite tokens with expiry validation and DynamoDB persistence.
Duel Link Endpoints
scripts/fastapi/routes/duels.py
Adds three new endpoints: POST /generate-duel-link, GET /duel-link/{token}, POST /accept-duel-link; integrates DuelInviteLinkOperations, wagering validation, and LeetCode GraphQL problem fetching.
Email Invitation Service
scripts/fastapi/email_service.py
Adds send_yeetcode_invite() function to send formatted HTML invite emails via Resend API with challenger/challengee context.
Email Invite Endpoint
scripts/fastapi/routes/auth.py
Adds SendInviteRequest model and POST /send-invite endpoint with email validation, rate limiting, and send_yeetcode_invite integration.
User Search Endpoint
scripts/fastapi/routes/users.py
Adds SearchUserRequest model and POST /search-user endpoint to check user existence on YeetCode first, then LeetCode GraphQL as fallback; normalizes XP field to integer across multiple endpoints.
XP Type Normalization
scripts/fastapi/background_tasks.py, scripts/fastapi/cache_operations.py
Casts XP to integer during processing and cache updates to handle string-stored XP values.
Startup & Health Endpoints
scripts/fastapi/main.py
Adds environment-conditional scheduler/background task startup (prod-only), new GET /health and GET /dev/info endpoints, updated app metadata and root endpoint response.
Admin Log Selection
scripts/fastapi/routes/admin.py
Updates get_log_content to dynamically select log file (fastapi-dev.log or fastapi.log) based on PORT environment variable; introduces verify_api_key_query helper.
Deployment Timestamps
scripts/fastapi/logger.py, scripts/fastapi/models.py, scripts/fastapi/scheduler.py, scripts/fastapi/cache_dumper.py, scripts/fastapi/cache_loader.py, scripts/fastapi/cache_manager.py, scripts/fastapi/discord_webhook.py, scripts/fastapi/auth.py, scripts/fastapi/wal_manager.py, scripts/fastapi/routes/bounties.py, scripts/fastapi/routes/daily.py, scripts/fastapi/routes/groups.py
Adds deployment timestamp comment to top of files; no functional changes.
Learn Section Components
src/components/LearnStep.jsx, src/components/learn/CodeEditor.jsx, src/components/learn/LivePreview.jsx, src/components/learn/ProblemPanel.jsx
Introduces LearnStep component with level/problem navigation, multi-tab code editor, live preview, and hints panel; CodeEditor wraps Monaco with custom options; LivePreview renders code in iframe with error handling.
Frontend Problems Dataset
src/data/frontendProblems.js
Exports comprehensive frontendProblems array and helper functions (getProblemById, getProblemsByLevel, etc.) covering HTML/CSS/JS basics and intermediate challenges organized by level.
App Layout Integration
src/components/App.jsx
Conditionally renders LearnStep when step='learn'; hides header and flame/streak UI during learn step.
Leaderboard Navigation
src/components/LeaderboardStep.jsx, src/components/leaderboard/LeaderboardHeader.jsx
Passes navigateToStep prop through LeaderboardStep to LeaderboardHeader; adds Learn button to header triggering navigation to 'learn' step.
Duel UI Restructure
src/components/leaderboard/DuelsSection.jsx
Adds "Anyone" mode alongside group duels; introduces email invite and link generation UI; refactors handleSendChallenge to support email-based invites for LeetCode-only users and duel link sharing.
Log Viewer UI
scripts/fastapi/static/log_viewer.html
Adds log sort order toggle button; reverses log display by default (latest first); updates button label on toggle.
IPC Handlers
src/index.js
Adds ipcMain handlers for search-user, generate-duel-link, get-duel-link-info, and send-invite to expose FastAPI endpoints to frontend.
Electron Preload API
src/preload.js
Exposes searchUser, sendInvite, generateDuelLink, getDuelLinkInfo methods via contextBridge with input validation.
Frontend Service Layer
src/services/duels.js
Adds searchUser, sendInvite, generateDuelLink, getDuelLinkInfo async functions that validate Electron API availability and delegate to preload methods.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Frontend (DuelsSection)
    participant IPC as Electron IPC
    participant FastAPI as FastAPI Backend
    participant DB as DynamoDB
    participant Email as Email Service

    User->>UI: Click "Generate Link"
    UI->>IPC: generateDuelLink(challenger, difficulty, isWager, amount)
    IPC->>FastAPI: POST /generate-duel-link
    FastAPI->>DB: Store invite record with token
    FastAPI-->>IPC: {token, invite_url, expires_at}
    IPC-->>UI: Link generated
    UI->>UI: Display link, show copy button
    
    User->>UI: Share link / or Send Email Invite
    alt Email Invite Path
        User->>UI: Search for LeetCode user, enter email
        UI->>IPC: sendInvite(challenger, challengee, email)
        IPC->>FastAPI: POST /send-invite
        FastAPI->>Email: send_yeetcode_invite(email, challenger, challengee)
        Email-->>FastAPI: Success
    else Link Share Path
        User->>UI: Copy link to clipboard
    end
    
    User->>UI: (Receiver) Accept link / Click invite link
    UI->>IPC: getDuelLinkInfo(token)
    IPC->>FastAPI: GET /duel-link/{token}
    FastAPI->>DB: Fetch invite, validate expiry
    FastAPI-->>IPC: {challenger, difficulty, is_wager, wager_amount, ...}
    IPC-->>UI: Invite details
    UI->>UI: Render challenge preview
    User->>UI: Accept challenge
    UI->>IPC: acceptDuelLink(token, accepting_user, opponent_wager?)
    IPC->>FastAPI: POST /accept-duel-link
    FastAPI->>DB: Create duel, validate wager constraints
    FastAPI->>DB: Auto-accept if wagered
    FastAPI-->>IPC: {duel_id, status, ...}
    IPC-->>UI: Duel created
Loading
sequenceDiagram
    participant User as User
    participant LearnUI as LearnStep Component
    participant Cache as Frontend State
    participant Preview as LivePreview iframe

    User->>LearnUI: Select Level
    LearnUI->>Cache: Load level, get first problem
    LearnUI->>LearnUI: Render problem panel, code editor, preview

    User->>LearnUI: Edit HTML/CSS/JS in tabs
    LearnUI->>Cache: Update code state
    Cache-->>LearnUI: Trigger re-render

    LearnUI->>Preview: Inject updated HTML/CSS/JS
    Preview->>Preview: Build document in iframe
    Preview-->>User: Live preview updates

    User->>LearnUI: Click "Show Solution"
    LearnUI->>Cache: Load solution code for problem
    LearnUI->>LearnUI: Populate editor tabs with solution
    Preview->>Preview: Update preview with solution code
    Preview-->>User: Preview shows solution result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • DuelInviteLinkOperations & invite link flow (scripts/fastapi/aws.py, scripts/fastapi/routes/duels.py): Token generation, expiry validation, wager constraint enforcement, and external LeetCode GraphQL integration for problem fetching require careful validation logic review.
  • Email invite integration (scripts/fastapi/email_service.py, scripts/fastapi/routes/auth.py, src/services/duels.js): Verify email validation, rate limiting, and end-to-end flow from UI to backend to email service.
  • DuelsSection UI restructuring (src/components/leaderboard/DuelsSection.jsx): Complex state management for "Anyone" vs "Group" modes, user search, email input, and link generation; multiple conditional branches and async handlers.
  • LearnStep and supporting components (src/components/LearnStep.jsx, src/components/learn/*): Verify state management for multi-tab code editor, live preview iframe injection with error handling, and problem/level navigation logic.
  • XP type coercion (scripts/fastapi/background_tasks.py, scripts/fastapi/cache_operations.py): Confirm that integer casting doesn't introduce data loss and that edge cases (null/missing XP) are handled consistently.
  • Main.py startup changes: Verify that scheduler and background tasks correctly start/stop based on environment and that graceful shutdown works in both dev and prod modes.
  • IPC/Preload input validation (src/index.js, src/preload.js, src/services/duels.js): Ensure all user inputs are properly validated before calling remote endpoints.

Possibly related PRs

  • pr test for coderabbit #3: Modifies src/components/leaderboard/DuelsSection.jsx to alter pending-duel UI, overlapping with the DuelsSection restructuring in this PR.

Poem

🐰 A rabbit hops with joy and cheer,
Duel links and learn steps drawing near!
Monaco codes in bright array,
Frontend lessons light the way,
And wagered duels sealed with care—
New challenges dance in the air! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the two main changes: reverse sort functionality for the log viewer and environment-aware routing logic for log file selection.
Docstring Coverage ✅ Passed Docstring coverage is 95.45% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining why this PR is needed, why this solution was chosen, and what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/fastapi/main.py (1)

244-252: Remove duplicate /health endpoint.

The /health endpoint is defined twice: once at lines 197-205 and again at lines 244-252. This will cause FastAPI to raise an error or use only the last definition. Remove the duplicate definition.

Apply this diff to remove the duplicate:

-
-@app.get("/health")
-async def health_check():
-    """Health check endpoint for deployment verification"""
-    return {
-        "status": "healthy",
-        "environment": args.env,
-        "port": PORT,
-        "timestamp": datetime.now().isoformat()
-    }
♻️ Duplicate comments (4)
scripts/fastapi/routes/bounties.py (1)

1-1: Verify the deployment timestamp accuracy.

Same timestamp inconsistency as noted in other files - the deployment timestamp shows 2024-12-19 but the PR was created on 2025-11-17.

scripts/fastapi/discord_webhook.py (1)

1-1: Verify the deployment timestamp accuracy.

Same timestamp inconsistency as previously noted.

scripts/fastapi/models.py (1)

1-1: Verify the deployment timestamp accuracy.

Same timestamp inconsistency as previously noted.

src/components/LearnStep.jsx (1)

99-99: Username prop mismatch (duplicate of App.jsx comment).

LearnStep accesses userData?.username but the userData object from App.jsx uses leetUsername field. See the comment on App.jsx line 929 for the fix.

🧹 Nitpick comments (12)
scripts/fastapi/cache_operations.py (1)

134-136: Good defensive coding for XP type handling.

The XP type coercion ensures numeric operations work correctly even if XP is stored as a string in the database. This defensive approach aligns with similar changes in background_tasks.py.

Optional simplification suggestion:

-        # Increment XP (ensure int conversion in case DB stored as string)
-        current_xp = int(user.get('xp', 0)) if user.get('xp') else 0
+        # Increment XP (ensure int conversion in case DB stored as string)
+        current_xp = int(user.get('xp') or 0)
scripts/fastapi/email_service.py (1)

86-86: Remove extraneous f-string prefix.

Line 86 uses an f-string without any placeholders. Consider using a regular string for clarity.

Apply this diff:

-                print(f"[DEBUG] No Resend API key, using mock email for development")
+                print("[DEBUG] No Resend API key, using mock email for development")
scripts/fastapi/routes/admin.py (1)

148-149: Consider security implications of exposing log file paths.

The error response includes the full log file path (detail=f"Log file not found at {log_path}"). While this aids debugging, consider whether exposing internal file paths to API consumers is appropriate for your security posture.

If you prefer to hide internal paths in production, apply this diff:

         if not os.path.exists(log_path):
-            raise HTTPException(status_code=404, detail=f"Log file not found at {log_path}")
+            raise HTTPException(status_code=404, detail="Log file not found")
scripts/fastapi/routes/users.py (1)

300-306: Consider extracting the GraphQL query to avoid duplication.

The USER_PROFILE_QUERY is defined inline here and appears to exist in src/utils/leetcode-queries.js (based on relevant snippets). Consider creating a shared Python constants file for GraphQL queries to maintain consistency across the codebase.

Create a new file scripts/fastapi/leetcode_queries.py:

USER_PROFILE_QUERY = """
    query getUserProfile($username: String!) {
        matchedUser(username: $username) {
            username
        }
    }
"""

Then import and use it:

+from leetcode_queries import USER_PROFILE_QUERY
+
 @router.post("/search-user")
 async def search_user_endpoint(
     request: SearchUserRequest,
     api_key: str = Depends(verify_api_key)
 ):
     """Search for a user on YeetCode and LeetCode"""
     try:
         username = request.username.lower()
         
         # First check YeetCode DB
         exists_on_yeetcode = False
         user_data = UserOperations.get_user_data(username)
         if user_data:
             exists_on_yeetcode = True
         
         # If not found on YeetCode, check LeetCode API
         exists_on_leetcode = False
         if not exists_on_yeetcode:
             try:
-                USER_PROFILE_QUERY = """
-                    query getUserProfile($username: String!) {
-                        matchedUser(username: $username) {
-                            username
-                        }
-                    }
-                """
-                
                 async with httpx.AsyncClient(timeout=10.0) as client:
scripts/fastapi/routes/auth.py (1)

99-102: Consider using Pydantic's EmailStr for email validation.

The challengee_email field currently uses str type with manual regex validation in the endpoint. Using Pydantic's EmailStr type (as in EmailOTPRequest) would provide automatic validation and improve consistency.

Apply this diff:

+from pydantic import BaseModel, EmailStr
+
 class SendInviteRequest(BaseModel):
     challenger_username: str
     challengee_username: str
-    challengee_email: str
+    challengee_email: EmailStr

Then remove the manual validation from the endpoint:

     email = request.challengee_email.lower()
     
-    # Validate email format
-    email_regex = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
-    if not re.match(email_regex, email):
-        raise HTTPException(
-            status_code=400,
-            detail="Invalid email format"
-        )
-    
     # Check rate limit
src/components/App.jsx (1)

882-883: Refactor complex conditional className for readability.

The className on line 882 contains deeply nested ternaries that are hard to parse. Consider extracting this logic into a helper function or using a more readable approach.

Apply this diff to improve readability:

+  // Helper to get container classes based on step
+  const getContainerClasses = () => {
+    const isLearn = step === 'learn';
+    const isLeaderboard = step === 'leaderboard';
+    const isWelcome = step === 'welcome';
+    
+    const width = isLearn ? 'w-full' : (isLeaderboard || isWelcome) ? 'max-w-7xl' : 'max-w-md';
+    const spacing = isLearn ? '' : 'mx-auto my-8 p-6';
+    const styling = isLearn ? '' : 'rounded-2xl shadow-2xl bg-white border-4 border-black';
+    const height = isLearn ? 'h-screen' : isLeaderboard ? 'min-h-[700px]' : 'min-h-[400px]';
+    const flexGap = isLearn ? '' : 'gap-6';
+    
+    return `${width} ${spacing} ${styling} ${height} flex flex-col ${flexGap}`;
+  };
+
   // UI
   return (
     <div
-      className={`w-full ${step === 'learn' ? '' : step === 'leaderboard' || step === 'welcome' ? 'max-w-7xl' : 'max-w-md'} ${step === 'learn' ? '' : 'mx-auto my-8 p-6 rounded-2xl shadow-2xl bg-white border-4 border-black'} ${step === 'leaderboard' ? 'min-h-[700px]' : step === 'learn' ? 'h-screen' : 'min-h-[400px]'} flex flex-col ${step === 'learn' ? '' : 'gap-6'}`}
+      className={getContainerClasses()}
       style={{ fontFamily: 'Space Grotesk, sans-serif' }}
     >
src/services/duels.js (1)

163-177: Inconsistent error handling in searchUser.

Lines 167-175 include special-case error handling that logs available API methods and suggests restarting the app. This is inconsistent with the other functions (sendInvite, generateDuelLink, getDuelLinkInfo) which use simpler error messages.

Either apply this enhanced error handling to all four new functions for consistency, or simplify searchUser to match the others.

For consistency, apply this diff to simplify searchUser:

 export const searchUser = async username => {
-  if (!window.electronAPI) {
-    throw new Error('Electron API not available. Please restart the app.');
-  }
-  if (!window.electronAPI.searchUser) {
-    console.error(
-      'Available electronAPI methods:',
-      Object.keys(window.electronAPI || {})
-    );
-    throw new Error(
-      'searchUser API not available. Please restart the app to load the latest changes.'
-    );
+  if (!window.electronAPI?.searchUser) {
+    throw new Error('searchUser API not available');
   }
   return await window.electronAPI.searchUser(username);
 };
src/index.js (1)

1741-1745: Consider production-friendly 404 error message.

The 404 error message on line 1743 says "Please restart the FastAPI server to register the new route" which is development-focused. In production, users cannot restart the server, and this message could be confusing.

Consider making this message environment-aware:

       if (error.response?.status === 404) {
+        const isDev = process.env.NODE_ENV === 'development';
         throw new Error(
-          'Endpoint not found. Please restart the FastAPI server to register the new route.'
+          isDev 
+            ? 'Endpoint not found. Please restart the FastAPI server to register the new route.'
+            : 'This feature is not available. Please update your app to the latest version.'
         );
       }
scripts/fastapi/aws.py (2)

1882-1915: Align wager validation and type hints in generate_duel_link

The invite generation path currently trusts callers for wager correctness:

  • wager_amount is typed as int = None, which is an implicit Optional[int] and conflicts with Ruff’s RUF013.
  • If is_wager is True but wager_amount is None or < 25, we still persist an invite with is_wager = 'Yes' and wager_amount = 0, which will later cause DuelOperations.create_duel to fail at acceptance time.

To make this more robust and consistent with create_duel:

  • Explicitly model the optional type and validate server‑side:
from typing import Optional

@staticmethod
def generate_duel_link(
    challenger_username: str,
    difficulty: str,
    is_wager: bool = False,
    wager_amount: Optional[int] = None,
) -> Dict:
    ...
    if is_wager:
        if wager_amount is None or wager_amount < 25:
            raise ValueError("Wager amount must be at least 25 XP")
    ...
    invite_data = {
        ...
        "is_wager": {"S": "Yes" if is_wager else "No"},
        "wager_amount": {"N": str(wager_amount or 0)},
        ...
    }

This keeps invites self‑consistent and fails fast if the backend is called directly with invalid wagers.


1941-1985: Avoid masking AWS/DynamoDB errors as “not found” in get_duel_link_info

get_duel_link_info catches a blanket Exception and returns None, which makes genuine AWS/DynamoDB failures indistinguishable from “token not found/expired” for callers.

Consider narrowing the exception type (e.g., to ClientError) and either:

  • Re‑raising unexpected exceptions so API layers can return a 5xx, or
  • Returning a structured error (e.g., {"success": False, "error": "Internal error"}) instead of None, so callers can differentiate internal failures from an invalid/expired token.

This will make debugging production issues around invite links much easier.

scripts/fastapi/routes/duels.py (1)

193-203: Cache hit path in get_duel_endpoint likely never matches duels

On cache hits you check:

for duel in cached_duels.get('data', []):
    if duel.get('id') == duel_id:
        return {"success": True, "data": duel}

But elsewhere (e.g., DuelOperations.create_duel and the frontend) the identifier field is consistently named duelId, not id. That means this cache path almost certainly never returns and you always fall back to DynamoDB.

Consider switching to duel.get('duelId') (or supporting both keys if you need backward compatibility) to actually leverage the DUELS cache here.

src/components/leaderboard/DuelsSection.jsx (1)

50-62: Duel link generation UX and state handling look solid, with minor polish opportunities

The new link-generation flow is wired coherently:

  • handleGenerateLink validates difficulty and wager constraints, checks current XP from leaderboard, and calls generateDuelLink with normalized usernames.
  • generatedLink/generatingLink state drives the UI, with difficulty changes clearing stale links in both Normal and Wager tabs.
  • handleCopyLink uses navigator.clipboard.writeText and surfaces success/failure via notifications and error.

A couple of small polish suggestions:

  • Consider guarding the notification message on the backend response (e.g., if (!result?.invite_url || result.success === false) { ... }) so we don’t show “Duel link generated!” if the IPC layer ever returns a structured error instead of throwing.
  • Since generatedLink is shared between tabs, you might want to clear it when switching mainTab as well, to avoid showing a Normal-tab link in the Wager UI (and vice versa).

These are nice-to-have; the current behavior should work correctly assuming the existing IPC wrappers throw on backend failures.

Also applies to: 1516-1576, 1647-1700, 1707-1754

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e60786d and 857004b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (35)
  • package.json (1 hunks)
  • scripts/fastapi/auth.py (1 hunks)
  • scripts/fastapi/aws.py (2 hunks)
  • scripts/fastapi/background_tasks.py (2 hunks)
  • scripts/fastapi/cache_dumper.py (1 hunks)
  • scripts/fastapi/cache_loader.py (1 hunks)
  • scripts/fastapi/cache_manager.py (1 hunks)
  • scripts/fastapi/cache_operations.py (2 hunks)
  • scripts/fastapi/discord_webhook.py (1 hunks)
  • scripts/fastapi/email_service.py (2 hunks)
  • scripts/fastapi/logger.py (1 hunks)
  • scripts/fastapi/main.py (5 hunks)
  • scripts/fastapi/models.py (1 hunks)
  • scripts/fastapi/routes/admin.py (2 hunks)
  • scripts/fastapi/routes/auth.py (2 hunks)
  • scripts/fastapi/routes/bounties.py (1 hunks)
  • scripts/fastapi/routes/daily.py (1 hunks)
  • scripts/fastapi/routes/duels.py (2 hunks)
  • scripts/fastapi/routes/groups.py (1 hunks)
  • scripts/fastapi/routes/users.py (4 hunks)
  • scripts/fastapi/scheduler.py (1 hunks)
  • scripts/fastapi/static/log_viewer.html (4 hunks)
  • scripts/fastapi/wal_manager.py (1 hunks)
  • src/components/App.jsx (3 hunks)
  • src/components/LeaderboardStep.jsx (2 hunks)
  • src/components/LearnStep.jsx (1 hunks)
  • src/components/leaderboard/DuelsSection.jsx (8 hunks)
  • src/components/leaderboard/LeaderboardHeader.jsx (2 hunks)
  • src/components/learn/CodeEditor.jsx (1 hunks)
  • src/components/learn/LivePreview.jsx (1 hunks)
  • src/components/learn/ProblemPanel.jsx (1 hunks)
  • src/data/frontendProblems.js (1 hunks)
  • src/index.js (1 hunks)
  • src/preload.js (1 hunks)
  • src/services/duels.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
src/components/LeaderboardStep.jsx (1)
src/components/App.jsx (1)
  • navigateToStep (327-333)
src/components/learn/ProblemPanel.jsx (2)
src/components/leaderboard/TodaysChallenge.jsx (1)
  • problem (284-284)
src/components/LearnStep.jsx (1)
  • showHints (19-19)
scripts/fastapi/routes/auth.py (3)
scripts/fastapi/models.py (2)
  • EmailOTPRequest (10-12)
  • EmailOTPResponse (15-19)
scripts/fastapi/auth.py (3)
  • verify_api_key (24-39)
  • check_rate_limit (42-56)
  • clear_rate_limit (59-63)
scripts/fastapi/email_service.py (1)
  • send_yeetcode_invite (77-134)
src/services/duels.js (2)
src/components/EmailStep.jsx (1)
  • email (11-11)
src/components/leaderboard/DuelsSection.jsx (1)
  • wagerAmount (47-47)
src/components/App.jsx (1)
src/components/LearnStep.jsx (1)
  • LearnStep (10-287)
src/components/LearnStep.jsx (4)
src/data/frontendProblems.js (6)
  • frontendProblems (3-617)
  • frontendProblems (3-617)
  • getLevelInfo (636-663)
  • getLevelInfo (636-663)
  • getProblemsByLevel (624-626)
  • getProblemsByLevel (624-626)
src/components/App.jsx (3)
  • animationClass (44-44)
  • navigateToStep (327-333)
  • userData (16-21)
src/components/learn/CodeEditor.jsx (1)
  • CodeEditor (4-36)
src/components/learn/LivePreview.jsx (1)
  • LivePreview (3-61)
src/components/leaderboard/LeaderboardHeader.jsx (1)
src/components/App.jsx (1)
  • navigateToStep (327-333)
src/preload.js (2)
src/components/EmailStep.jsx (1)
  • email (11-11)
src/components/leaderboard/DuelsSection.jsx (1)
  • wagerAmount (47-47)
scripts/fastapi/main.py (2)
scripts/fastapi/logger.py (1)
  • info (87-89)
scripts/fastapi/scheduler.py (2)
  • start_scheduler (83-99)
  • stop_scheduler (102-110)
src/components/leaderboard/DuelsSection.jsx (3)
src/components/App.jsx (2)
  • leaderboard (23-23)
  • addNotification (252-261)
src/services/duels.js (8)
  • generateDuelLink (209-224)
  • generateDuelLink (209-224)
  • searchUser (163-177)
  • searchUser (163-177)
  • createDuel (37-54)
  • createDuel (37-54)
  • sendInvite (186-199)
  • sendInvite (186-199)
src/components/SearchableDropdown.jsx (1)
  • SearchableDropdown (3-200)
scripts/fastapi/routes/users.py (3)
scripts/fastapi/aws.py (2)
  • UserOperations (77-370)
  • get_user_data (81-100)
scripts/fastapi/auth.py (1)
  • verify_api_key (24-39)
src/utils/leetcode-queries.js (1)
  • USER_PROFILE_QUERY (26-32)
scripts/fastapi/routes/duels.py (2)
scripts/fastapi/aws.py (8)
  • DuelOperations (1177-1876)
  • DuelInviteLinkOperations (1879-1985)
  • UserOperations (77-370)
  • generate_duel_link (1883-1938)
  • get_duel_link_info (1941-1985)
  • get_user_data (81-100)
  • create_duel (1216-1310)
  • accept_duel (1313-1382)
scripts/fastapi/cache_manager.py (3)
  • get (86-97)
  • invalidate_all (118-126)
  • CacheType (32-41)
scripts/fastapi/cache_operations.py (1)
scripts/fastapi/cache_manager.py (1)
  • get (86-97)
src/index.js (1)
src/utils/config.js (1)
  • config (15-46)
🪛 Biome (2.1.2)
src/data/frontendProblems.js

[error] 2-619: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)


[error] 620-624: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)


[error] 624-628: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)


[error] 628-632: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)


[error] 632-636: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)

src/services/duels.js

[error] 162-177: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)


[error] 185-199: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)


[error] 208-224: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)


[error] 230-236: Illegal use of an export declaration outside of a module

not allowed inside scripts

(parse)

🪛 Ruff (0.14.5)
scripts/fastapi/routes/auth.py

108-108: Unused function argument: api_key

(ARG001)


150-150: Do not catch blind exception: Exception

(BLE001)


153-156: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


155-155: Use explicit conversion flag

Replace with conversion flag

(RUF010)

scripts/fastapi/aws.py

1887-1887: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


1894-1894: Abstract raise to an inner function

(TRY301)


1894-1894: Create your own exception

(TRY002)


1894-1894: Avoid specifying long messages outside the exception class

(TRY003)


1938-1938: Use raise without specifying exception name

Remove exception name

(TRY201)


1947-1947: Abstract raise to an inner function

(TRY301)


1947-1947: Create your own exception

(TRY002)


1947-1947: Avoid specifying long messages outside the exception class

(TRY003)


1982-1982: Do not catch blind exception: Exception

(BLE001)

scripts/fastapi/routes/users.py

284-284: Unused function argument: api_key

(ARG001)


325-325: Do not catch blind exception: Exception

(BLE001)


331-335: Consider moving this statement to an else block

(TRY300)


336-336: Do not catch blind exception: Exception

(BLE001)

scripts/fastapi/email_service.py

74-74: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


74-74: Create your own exception

(TRY002)


74-74: Avoid specifying long messages outside the exception class

(TRY003)


74-74: Use explicit conversion flag

Replace with conversion flag

(RUF010)


86-86: f-string without any placeholders

Remove extraneous f prefix

(F541)


131-131: Do not catch blind exception: Exception

(BLE001)


133-133: Use explicit conversion flag

Replace with conversion flag

(RUF010)


134-134: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


134-134: Create your own exception

(TRY002)


134-134: Avoid specifying long messages outside the exception class

(TRY003)


134-134: Use explicit conversion flag

Replace with conversion flag

(RUF010)

scripts/fastapi/routes/duels.py

217-217: Unused function argument: api_key

(ARG001)


227-227: Consider moving this statement to an else block

(TRY300)


228-228: Do not catch blind exception: Exception

(BLE001)


245-245: Do not catch blind exception: Exception

(BLE001)


260-260: Unused function argument: api_key

(ARG001)


284-284: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


336-336: Standard pseudo-random generators are not suitable for cryptographic purposes

(S311)


383-383: Consider moving this statement to an else block

(TRY300)


385-385: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (28)
scripts/fastapi/logger.py (1)

1-1: Metadata-only change; no functional impact.

The deployment timestamp comment is a non-functional metadata addition that does not affect logger behavior, configuration, or public API.

scripts/fastapi/scheduler.py (1)

1-1: Metadata-only change; no functional impact.

The deployment timestamp comment is a non-functional metadata addition that does not affect scheduler configuration, job definitions, or runtime behavior.

scripts/fastapi/wal_manager.py (1)

1-1: Metadata-only change; no functional impact.

The deployment timestamp comment is a non-functional metadata addition that does not affect WAL initialization, durability, or replay logic.

scripts/fastapi/routes/groups.py (1)

1-1: Metadata-only change; no functional impact.

The deployment timestamp comment is a non-functional metadata addition that does not affect group endpoints, cache operations, or database queries.

scripts/fastapi/auth.py (1)

1-1: Metadata-only change; no functional impact.

The deployment timestamp comment is a non-functional metadata addition that does not affect API key verification, rate limiting, or security checks.

scripts/fastapi/routes/daily.py (1)

1-1: Metadata-only change; no functional impact.

The deployment timestamp comment is a non-functional metadata addition that does not affect daily problem endpoints, caching, or completion tracking.

scripts/fastapi/cache_manager.py (1)

1-1: Metadata-only change; no functional impact.

The deployment timestamp comment is a non-functional metadata addition that does not affect cache initialization, TTL management, refresh scheduling, or WAL integration.

scripts/fastapi/cache_loader.py (1)

1-1: Metadata-only change; no functional impact.

The deployment timestamp comment is a non-functional metadata addition that does not affect cache loading, DynamoDB scans, or data normalization.

scripts/fastapi/background_tasks.py (1)

148-149: Good defensive coding for XP type handling.

The XP type coercion is consistent with the changes in cache_operations.py and ensures numeric operations work correctly regardless of how XP is stored in the database.

scripts/fastapi/static/log_viewer.html (1)

595-596: Verify the default sort state consistency.

The logs are reversed here to show latest first by default, but the isReversed state remains false. This creates a mismatch between the actual sort order and the state variable, which affects the button label accuracy.

Consider initializing isReversed = true on line 464 to reflect that logs are reversed by default, or adjust the button label logic to match the actual default behavior.

package.json (1)

117-117: Version is current and secure—no action needed.

The specified version 4.7.0 is the latest available, and no security advisories were found. The dependency addition is appropriate for the Monaco editor integration in the learn section.

scripts/fastapi/email_service.py (1)

77-134: LGTM! Email invite implementation follows established patterns.

The send_yeetcode_invite function correctly mirrors the structure of send_email_otp with appropriate mock mode fallback, debug logging, and HTML email formatting.

scripts/fastapi/routes/admin.py (1)

141-146: Environment-aware log routing implemented correctly.

The logic correctly selects between development and production log files based on the PORT environment variable as described in the PR objectives.

scripts/fastapi/routes/users.py (2)

41-46: LGTM! XP field normalization prevents frontend errors.

Ensuring the xp field always exists with a default value of 0 and is cast to an integer prevents calculation errors in the frontend.


281-339: New user search endpoint implemented with cross-platform fallback.

The endpoint correctly checks YeetCode first, then falls back to LeetCode's GraphQL API. The implementation includes appropriate timeout handling (10 seconds) and graceful error handling for LeetCode API failures.

scripts/fastapi/main.py (1)

77-93: Environment-aware startup improves development experience.

Conditionally disabling the scheduler and background tasks in development mode while creating dummy tasks for clean shutdown is a solid approach. This prevents resource contention and simplifies local development.

scripts/fastapi/routes/auth.py (1)

105-157: Invite endpoint correctly implements rate limiting and email delivery.

The endpoint properly validates input, enforces rate limits, and handles errors by raising appropriate HTTP exceptions. The implementation follows established patterns from the /send-otp endpoint.

src/components/leaderboard/LeaderboardHeader.jsx (1)

49-55: LGTM! New Learn UI button follows existing patterns.

The button implementation is consistent with the existing "Leave Group" button styling and correctly invokes the navigateToStep prop to navigate to the learn section.

src/components/LeaderboardStep.jsx (1)

26-26: LGTM! Navigation prop correctly passed through to header.

The navigateToStep prop is properly added to the component signature and passed through to LeaderboardHeader, enabling the new Learn UI navigation functionality.

Also applies to: 57-57

src/components/learn/CodeEditor.jsx (1)

1-38: LGTM! Clean Monaco editor wrapper with appropriate configuration.

The CodeEditor component provides a well-configured Monaco editor with sensible defaults for a learning environment (disabled minimap, enabled word wrap, larger font size, appropriate line height). The custom loading UI enhances user experience.

src/components/learn/ProblemPanel.jsx (1)

1-95: LGTM!

The component is well-structured with clear separation of concerns. The difficulty color mapping is straightforward, and the collapsible hints section provides good UX.

src/data/frontendProblems.js (1)

1-663: LGTM! Well-structured problem data and helpers.

The problem dataset is comprehensive with consistent structure across all entries. The helper functions are straightforward and correctly implemented.

Note: The static analysis errors about "export outside of a module" are false positives - this file works correctly in the Vite/React environment.

src/services/duels.js (1)

158-236: Service functions correctly delegate to electronAPI.

The four new functions (searchUser, sendInvite, generateDuelLink, getDuelLinkInfo) follow the established pattern in this file and correctly check for API availability before delegating. The parameter handling for optional wager fields is appropriate.

src/index.js (1)

1673-1810: IPC handlers correctly implement duel invite features.

The four new IPC handlers (search-user, generate-duel-link, get-duel-link-info, send-invite) follow established patterns in this file:

  • Proper use of axios with config.fastApiUrl and config.apiKey
  • Consistent 10-second timeout
  • Appropriate error handling and logging
  • Correct parameter mapping to FastAPI endpoints

The public endpoint handling for get-duel-link-info (no Authorization header) is correctly implemented per the comment.

src/components/LearnStep.jsx (1)

1-289: Well-structured learning component with proper state management.

The LearnStep component demonstrates good React practices:

  • Clean state initialization and synchronization via useEffect
  • Proper event handler delegation (handleLevelChange → handleProblemChange)
  • Functional state updates where appropriate
  • Consistent flag resets on problem changes

The integration with CodeEditor and LivePreview is seamless, and the UI layout provides a good learning experience.

src/preload.js (1)

328-377: Input validation for new methods is thorough and correct.

The four new electronAPI methods properly validate all inputs before invoking IPC:

  • Username and email validation using existing helpers
  • Difficulty validation against allowed values
  • Wager amount range validation (25-1,000,000 XP) with descriptive errors
  • Token format validation

The optional wager parameter handling with defaults (lines 367-368) is implemented correctly.

scripts/fastapi/routes/duels.py (1)

207-232: New duel-link endpoints are consistent with AWS operations

The GenerateDuelLinkRequest/AcceptDuelLinkRequest models and the /generate-duel-link and /duel-link/{token} endpoints are wired cleanly into DuelInviteLinkOperations, with appropriate auth on generation and a public read endpoint for consumption.

Once the get_duel_link_info error handling is tightened in aws.py, these routes should behave predictably and align with the frontend expectations (token + invite_url + expires_at).

Also applies to: 234-248

src/components/leaderboard/DuelsSection.jsx (1)

1379-1414: “Anyone” vs “Group Members” UI is clear and resets related state appropriately

The new header controls and conditional rendering for “Group Members” vs “Anyone” mode are well thought out:

  • Toggling modes resets searchUsername, searchResult, inviteEmail, and selectedFriend to avoid stale selections leaking across modes.
  • The “Anyone” view clearly distinguishes:
    • YeetCode users (immediate challenge),
    • LeetCode-only users (prompt for email invite),
    • Unknown usernames (error).
  • The fallback to the existing SearchableDropdown for group members keeps the original UX intact.

No functional issues spotted here; the state resets should prevent most edge-case confusion around switching modes.

Also applies to: 1417-1481

@@ -1,3 +1,4 @@
# Deployment timestamp: 2024-12-19 00:00:00 UTC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Verify the deployment timestamp accuracy.

The deployment timestamp shows 2024-12-19 00:00:00 UTC, but this PR was created on 2025-11-17. This backdated timestamp could cause confusion for deployment tracking and version history.

🤖 Prompt for AI Agents
In scripts/fastapi/cache_dumper.py around line 1 the hardcoded deployment
timestamp "2024-12-19 00:00:00 UTC" is inaccurate for this PR; replace it with
the correct deployment timestamp (e.g., 2025-11-17 with appropriate time) or
remove the static timestamp and switch to a dynamic approach (compute/inject the
build/deploy time at CI/runtime) so the file reflects accurate, automatically
maintained deployment metadata.

@@ -1,3 +1,4 @@
# Deployment timestamp: 2024-12-19 00:00:00 UTC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Verify the deployment timestamp.

The deployment timestamp shows 2024-12-19 00:00:00 UTC, which is in the past relative to the current date (November 2025). Ensure this timestamp reflects the actual deployment date or intended release date.

🤖 Prompt for AI Agents
scripts/fastapi/email_service.py around line 1 contains a hard-coded deployment
timestamp "2024-12-19 00:00:00 UTC" which is outdated; update this to the
correct intended deployment/release date or remove the static timestamp and
replace it with a dynamic value injected at build/deploy time (e.g., CI/CD
variable or environment variable) so it always reflects the actual deployment
time; ensure the timestamp uses a standard ISO UTC format and commit the
corrected value or implementation.

Comment thread scripts/fastapi/main.py
Comment on lines +208 to +241
@app.get("/dev/info")
async def dev_info():
"""Developer information endpoint - shows environment and configuration details"""
return {
"environment": {
"type": args.env,
"port": PORT,
"host": HOST,
"debug_mode": DEBUG_MODE
},
"features": {
"scheduler_enabled": args.env == 'prod',
"background_tasks_enabled": args.env == 'prod',
"duel_monitoring": args.env == 'prod',
"cleanup_tasks": args.env == 'prod'
},
"endpoints": {
"health": f"http://0.0.0.0:{PORT}/",
"docs": f"http://0.0.0.0:{PORT}/docs",
"redoc": f"http://0.0.0.0:{PORT}/redoc",
"dev_info": f"http://0.0.0.0:{PORT}/dev/info",
"cache_stats": f"http://0.0.0.0:{PORT}/cache/stats"
},
"database": {
"users_table": os.getenv("USERS_TABLE"),
"daily_table": os.getenv("DAILY_TABLE"),
"duels_table": os.getenv("DUELS_TABLE"),
"bounties_table": os.getenv("BOUNTIES_TABLE")
},
"message": "YeetCode Developer API Info",
"production_url": "http://209.182.234.33:6969",
"development_url": "http://209.182.234.33:42069",
"timestamp": datetime.now().isoformat()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Consider access control for the /dev/info endpoint.

The /dev/info endpoint exposes detailed environment configuration including database table names, ports, and internal URLs. Consider adding authentication (similar to admin routes using verify_api_key) or restricting this endpoint to development environments only.

Apply this diff to restrict to development or add authentication:

Option 1: Restrict to development only

 @app.get("/dev/info")
 async def dev_info():
     """Developer information endpoint - shows environment and configuration details"""
+    if args.env != 'dev':
+        raise HTTPException(status_code=404, detail="Not found")
     return {

Option 2: Add authentication

+from auth import verify_api_key
+
 @app.get("/dev/info")
-async def dev_info():
+async def dev_info(api_key: str = Depends(verify_api_key)):
     """Developer information endpoint - shows environment and configuration details"""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@app.get("/dev/info")
async def dev_info():
"""Developer information endpoint - shows environment and configuration details"""
return {
"environment": {
"type": args.env,
"port": PORT,
"host": HOST,
"debug_mode": DEBUG_MODE
},
"features": {
"scheduler_enabled": args.env == 'prod',
"background_tasks_enabled": args.env == 'prod',
"duel_monitoring": args.env == 'prod',
"cleanup_tasks": args.env == 'prod'
},
"endpoints": {
"health": f"http://0.0.0.0:{PORT}/",
"docs": f"http://0.0.0.0:{PORT}/docs",
"redoc": f"http://0.0.0.0:{PORT}/redoc",
"dev_info": f"http://0.0.0.0:{PORT}/dev/info",
"cache_stats": f"http://0.0.0.0:{PORT}/cache/stats"
},
"database": {
"users_table": os.getenv("USERS_TABLE"),
"daily_table": os.getenv("DAILY_TABLE"),
"duels_table": os.getenv("DUELS_TABLE"),
"bounties_table": os.getenv("BOUNTIES_TABLE")
},
"message": "YeetCode Developer API Info",
"production_url": "http://209.182.234.33:6969",
"development_url": "http://209.182.234.33:42069",
"timestamp": datetime.now().isoformat()
}
@app.get("/dev/info")
async def dev_info():
"""Developer information endpoint - shows environment and configuration details"""
if args.env != 'dev':
raise HTTPException(status_code=404, detail="Not found")
return {
"environment": {
"type": args.env,
"port": PORT,
"host": HOST,
"debug_mode": DEBUG_MODE
},
"features": {
"scheduler_enabled": args.env == 'prod',
"background_tasks_enabled": args.env == 'prod',
"duel_monitoring": args.env == 'prod',
"cleanup_tasks": args.env == 'prod'
},
"endpoints": {
"health": f"http://0.0.0.0:{PORT}/",
"docs": f"http://0.0.0.0:{PORT}/docs",
"redoc": f"http://0.0.0.0:{PORT}/redoc",
"dev_info": f"http://0.0.0.0:{PORT}/dev/info",
"cache_stats": f"http://0.0.0.0:{PORT}/cache/stats"
},
"database": {
"users_table": os.getenv("USERS_TABLE"),
"daily_table": os.getenv("DAILY_TABLE"),
"duels_table": os.getenv("DUELS_TABLE"),
"bounties_table": os.getenv("BOUNTIES_TABLE")
},
"message": "YeetCode Developer API Info",
"production_url": "http://209.182.234.33:6969",
"development_url": "http://209.182.234.33:42069",
"timestamp": datetime.now().isoformat()
}
🤖 Prompt for AI Agents
In scripts/fastapi/main.py around lines 208 to 241, the /dev/info endpoint
currently returns sensitive environment and config details; restrict access by
either (A) gating the route so it only registers or returns data when args.env
indicates a non-production development environment (e.g., args.env in
['dev','local']), returning 404/403 otherwise, or (B) requiring existing
authentication middleware by adding the verify_api_key dependency or decorator
to the route and validating the key before returning any payload; also remove or
redact highly sensitive values (real IPs, production_url) from the response when
not explicitly authorized.

@@ -1,3 +1,4 @@
# Deployment timestamp: 2024-12-19 00:00:00 UTC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Verify the deployment timestamp.

The deployment timestamp shows 2024-12-19 00:00:00 UTC, which is in the past. Confirm this is the intended timestamp.

🤖 Prompt for AI Agents
In scripts/fastapi/routes/admin.py at line 1 the hardcoded deployment timestamp
reads "2024-12-19 00:00:00 UTC" which is in the past; confirm whether this exact
timestamp is intended and if not either update it to the correct deployment
datetime or remove the static timestamp and replace it with a dynamic/generated
value (e.g., set at build/deploy time or generated via datetime.utcnow()) so it
reflects the actual deployment; after updating, run tests or a quick local check
and commit the change.

Comment on lines +251 to +255
class AcceptDuelLinkRequest(BaseModel):
token: str
accepting_username: str
opponent_wager: int = None # For wager duels

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Harden accept-duel-link for missing difficulty and token lifecycle

The core flow looks good, but there are a couple of edge cases worth addressing:

  1. Potential crash when difficulty is missing
difficulty = link_info.get("difficulty")
...
leetcode_difficulty = difficulty_map.get(difficulty, difficulty.upper())

If for any reason difficulty is missing or None (bad data, future schema change, manual writes), this will raise AttributeError on .upper() and surface as a 500 instead of a clean error.

Consider guarding this:

difficulty = link_info.get("difficulty") or "Random"

difficulty_map = {
    "Easy": "EASY",
    "Medium": "MEDIUM",
    "Hard": "HARD",
    "Random": ["EASY", "MEDIUM", "HARD"][random.randint(0, 2)],
}

mapped = difficulty_map.get(difficulty)
if not mapped:
    return {"success": False, "error": f"Unsupported difficulty: {difficulty}"}

leetcode_difficulty = mapped
  1. Invite tokens remain reusable after successful acceptance

After successfully creating (and for wagers, auto‑accepting) a duel, the invite row in USERS_TABLE is left intact. That means the same link can be reused to spin up multiple duels, which may not match the expected “single‑use invite” semantics and also leaves stale rows around until TTL.

If the intended behavior is one‑time use, consider deleting the invite on success:

if result.get("success"):
    # Best-effort delete of the invite token
    try:
        ddb.delete_item(
            TableName=USERS_TABLE,
            Key={"username": {"S": f"duel_invite_{request.token}"}},
        )
    except Exception as cleanup_error:
        if DEBUG_MODE:
            print(f"[WARN] Failed to delete used duel invite token: {cleanup_error}")

This prevents accidental reuse and keeps the table tidier.

Also applies to: 257-384

Comment on lines +928 to +935
// Reverse sort button
const reverseSortBtn = document.getElementById('reverseSortBtn');
reverseSortBtn.addEventListener('click', () => {
isReversed = !isReversed;
allLogs.reverse();
reverseSortBtn.textContent = isReversed ? '⬇️ Latest First' : '⬆️ Oldest First';
filterLogs();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the reverse sort button label logic.

The button label logic appears inverted. When isReversed is toggled to true (line 931), the button displays "⬇️ Latest First" (line 933), but at this point the logs have been reversed from their default latest-first order, making them oldest-first.

Apply this fix to correct the label logic:

         reverseSortBtn.addEventListener('click', () => {
             isReversed = !isReversed;
             allLogs.reverse();
-            reverseSortBtn.textContent = isReversed ? '⬇️ Latest First' : '⬆️ Oldest First';
+            reverseSortBtn.textContent = isReversed ? '⬆️ Oldest First' : '⬇️ Latest First';
             filterLogs();
         });

Or alternatively, initialize isReversed = true on line 464 since logs are reversed by default on line 596.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Reverse sort button
const reverseSortBtn = document.getElementById('reverseSortBtn');
reverseSortBtn.addEventListener('click', () => {
isReversed = !isReversed;
allLogs.reverse();
reverseSortBtn.textContent = isReversed ? '⬇️ Latest First' : '⬆️ Oldest First';
filterLogs();
});
// Reverse sort button
const reverseSortBtn = document.getElementById('reverseSortBtn');
reverseSortBtn.addEventListener('click', () => {
isReversed = !isReversed;
allLogs.reverse();
reverseSortBtn.textContent = isReversed ? '⬆️ Oldest First' : '⬇️ Latest First';
filterLogs();
});
🤖 Prompt for AI Agents
In scripts/fastapi/static/log_viewer.html around lines 928-935, the reverse sort
button labels are inverted: after toggling isReversed to true and reversing
allLogs the view becomes oldest-first but the button still shows "⬇️ Latest
First". Fix by swapping the two label strings so that when isReversed is true
the textContent is '⬆️ Oldest First' and when false it is '⬇️ Latest First';
alternatively (instead of swapping) set the initial isReversed = true at its
declaration around line 464 to match the default reversed state at line 596.

Comment thread src/components/App.jsx
quickActionsProps={{ handleLogout }}
/>
)}
{step === 'learn' && <LearnStep {...stepProps} />}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify userData prop mismatch with LearnStep.

App.jsx passes userData to LearnStep, but LearnStep accesses userData?.username on line 99, while the userData object in App.jsx uses leetUsername (line 20). This will result in "User" being displayed instead of the actual username.

Run this script to verify the userData structure:

If confirmed, apply this fix to LearnStep.jsx line 99:

-        <div className="text-white font-semibold text-xl bg-white/20 px-6 py-3 rounded-xl">
-          {userData?.username || 'User'}
-        </div>
+        <div className="text-white font-semibold text-xl bg-white/20 px-6 py-3 rounded-xl">
+          {userData?.leetUsername || userData?.name || 'User'}
+        </div>

🏁 Script executed:

#!/bin/bash
# Check userData structure in App.jsx and its usage in LearnStep
rg -n "userData\s*[=:].*{" src/components/App.jsx -A 5
rg -n "userData\?\.username" src/components/LearnStep.jsx
rg -n "userData\?\.leetUsername" src/components/

Length of output: 1196


I need to examine the actual userData structure in the codebase directly. Let me run a more targeted script.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find userData initialization in App.jsx around line 20
sed -n '1,30p' src/components/App.jsx | grep -n "userData\|leetUsername\|username"

# Find how userData state is initialized
rg -n "useState.*userData|userData.*useState" src/components/App.jsx -A 3

# Look for where userData object is created/constructed
rg -n "userData\s*=" src/components/App.jsx | head -20

Length of output: 179



---

🏁 Script executed:

```shell
#!/bin/bash
# Check how userData is passed from App.jsx to LearnStep
rg -n "stepProps\s*=" src/components/App.jsx -B 3 -A 10 | head -30

# Verify LearnStep component receives userData and how it's used
rg -n "userData" src/components/LearnStep.jsx -C 2

Length of output: 781


Fix userData?.username to userData?.leetUsername in LearnStep.

The userData object in App.jsx (lines 16-20) contains leetUsername and name fields, not username. LearnStep.jsx line 99 accesses the non-existent userData?.username property, causing it to always display "User" instead of the actual username.

-          {userData?.username || 'User'}
+          {userData?.leetUsername || userData?.name || 'User'}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/App.jsx around line 929 and src/components/LearnStep.jsx
around line 99, LearnStep currently reads userData?.username which doesn't exist
on the userData object (it has leetUsername and name). Update LearnStep to use
userData?.leetUsername (or fall back to name) where it currently references
userData?.username so the real username is displayed.

Comment on lines +169 to +220
// Handle generating a duel invite link
const handleGenerateLink = async () => {
if (!selectedDifficulty) {
setError('Please select a problem difficulty first!');
return;
}

setError('');
setGeneratingLink(true);

try {
const isWager = mainTab === 'wager';
const wagerNum = isWager ? parseInt(wagerAmount) : null;

// Validate wager amount for wager duels
if (isWager) {
if (!wagerAmount || isNaN(wagerNum) || wagerNum < 25) {
setError('Wager amount must be at least 25 XP!');
setGeneratingLink(false);
return;
}

// Check if challenger has enough XP
const currentUserData = leaderboard.find(
u => u.username === normalizedCurrentUser
);
const currentUserXP = currentUserData?.xp || 0;
if (currentUserXP < wagerNum) {
setError(
`You don't have enough XP! (Have: ${currentUserXP}, Need: ${wagerNum})`
);
setGeneratingLink(false);
return;
}
}

const result = await generateDuelLink(
normalizedCurrentUser,
selectedDifficulty,
isWager,
wagerNum
);

setGeneratedLink(result);
addNotification('Duel link generated! Copy and share it.', 'success');
} catch (err) {
console.error('Error generating link:', err);
setError(err.message || 'Failed to generate link');
} finally {
setGeneratingLink(false);
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix duelMode interaction so wager tab isn’t accidentally forced into “anyone” flow

The new “Anyone” mode, search, invite, and link-generation flows are a nice addition, but there’s a subtle state coupling:

  • duelMode is toggled only in the Normal tab.
  • handleSendChallenge branches solely on duelMode:
const handleSendChallenge = async () => {
  ...
  // Handle "Anyone" mode
  if (duelMode === 'anyone') {
    ...
  }

  // Regular group member challenge flow
  ...
}

If a user switches Normal → “Anyone”, then navigates to the Wager tab and clicks “Send Wager Challenge”, duelMode is still 'anyone', so the function enters the “anyone” branch. Because the wager tab doesn’t expose searchUsername/searchResult, this path ends up with the generic “Please search for a user first!” error, effectively blocking wager duels until the user manually toggles back to “Group Members”.

A minimal fix is to gate the “anyone” logic on the active tab:

const handleSendChallenge = async () => {
  setError('');

  const isAnyoneMode = mainTab === 'normal' && duelMode === 'anyone';

  if (isAnyoneMode) {
    // existing anyone-mode logic...
    return;
  }

  // existing group/wager logic...
};

This keeps the wager tab behavior independent of the Normal-tab mode while preserving the new flows.

The rest of the “anyone” handling (search via searchUser, conditional invite via sendInvite, basic email validation, and clearing of search/link state) looks good and is consistent with the backend behavior.

Also applies to: 235-263, 269-380

Comment on lines +36 to +38
} catch (error) {
console.error('Error in user code:', error);
document.body.innerHTML = '<div style="padding: 20px; background: #fee2e2; color: #991b1b; border-left: 4px solid #dc2626; margin: 20px; font-family: monospace; white-space: pre-wrap;"><strong>Error:</strong>\\n' + error.toString() + '</div>' + document.body.innerHTML;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential XSS in error display.

Line 38 constructs HTML with error.toString() without sanitization. If an error message contains user-controlled data (e.g., from malicious JS code), it could inject arbitrary HTML/JS into the error display.

Apply this diff to safely escape the error message:

             } catch (error) {
               console.error('Error in user code:', error);
-              document.body.innerHTML = '<div style="padding: 20px; background: #fee2e2; color: #991b1b; border-left: 4px solid #dc2626; margin: 20px; font-family: monospace; white-space: pre-wrap;"><strong>Error:</strong>\\n' + error.toString() + '</div>' + document.body.innerHTML;
+              const errorDiv = document.createElement('div');
+              errorDiv.style.cssText = 'padding: 20px; background: #fee2e2; color: #991b1b; border-left: 4px solid #dc2626; margin: 20px; font-family: monospace; white-space: pre-wrap;';
+              const errorText = document.createTextNode('Error:\\n' + error.toString());
+              const strong = document.createElement('strong');
+              strong.textContent = 'Error:';
+              errorDiv.appendChild(strong);
+              errorDiv.appendChild(document.createTextNode('\\n' + error.toString()));
+              document.body.insertBefore(errorDiv, document.body.firstChild);
             }
🤖 Prompt for AI Agents
In src/components/learn/LivePreview.jsx around lines 36 to 38, the error display
injects error.toString() into innerHTML which can lead to XSS; replace this by
creating DOM nodes and inserting the message as text (use element.textContent or
createTextNode) instead of concatenating into innerHTML, build the wrapper div
with the same inline styles via element.style or classNames, set its textContent
to a descriptive error string plus error.toString() (escaped by textContent),
and then prepend/insert that element into document.body rather than mutating
document.body.innerHTML.

<iframe
ref={iframeRef}
title="preview"
sandbox="allow-scripts allow-same-origin allow-forms allow-modals allow-popups"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Security risk: allow-same-origin combined with allow-scripts.

The sandbox allows both allow-scripts and allow-same-origin, which defeats the sandbox security model. With both permissions, the iframe can access the parent window's origin, potentially reading cookies, localStorage, and manipulating the parent DOM.

For a learning environment where users write arbitrary code, remove allow-same-origin unless absolutely necessary. If you need it for a specific feature, document the risk and implement additional safeguards.

Apply this diff:

-        sandbox="allow-scripts allow-same-origin allow-forms allow-modals allow-popups"
+        sandbox="allow-scripts allow-forms allow-modals allow-popups"

If allow-same-origin is required, explain why in a comment and add CSP headers or other protections.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sandbox="allow-scripts allow-same-origin allow-forms allow-modals allow-popups"
sandbox="allow-scripts allow-forms allow-modals allow-popups"
🤖 Prompt for AI Agents
In src/components/learn/LivePreview.jsx at line 55, the iframe sandbox currently
includes both allow-scripts and allow-same-origin which defeats the sandbox
protection; remove allow-same-origin from the sandbox attribute so it reads
sandbox="allow-scripts allow-forms allow-modals allow-popups" and use
postMessage for any parent/iframe communication; if allow-same-origin truly is
required, add an inline comment explaining the necessity, document the risk, and
implement additional mitigations such as setting strict Content-Security-Policy
headers (frame-ancestors, script-src, default-src), origin isolation, and
restricting cookies (SameSite/HttpOnly) or other server-side protections before
restoring allow-same-origin.

@SidmoGoesBrrr

Copy link
Copy Markdown
Collaborator Author

Closing - PR contained too many unrelated changes. Creating focused PR with only log viewer changes.

This branch was previously deployed

1 inactive deployment
env — 857004b9 Deployed Nov 17, 2025 by SidmoGoesBrrr via deploy #74
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