Add reverse sort to log viewer and environment-aware routing - #22
SidmoGoesBrrr wants to merge 22 commits into
Conversation
- 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>
pushing for pr
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>
WalkthroughThis 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Example instruction:
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. Comment |
There was a problem hiding this comment.
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/healthendpoint.The
/healthendpoint 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?.usernamebut the userData object from App.jsx usesleetUsernamefield. 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_QUERYis defined inline here and appears to exist insrc/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'sEmailStrfor email validation.The
challengee_emailfield currently usesstrtype with manual regex validation in the endpoint. Using Pydantic'sEmailStrtype (as inEmailOTPRequest) 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: EmailStrThen 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 limitsrc/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 ingenerate_duel_linkThe invite generation path currently trusts callers for wager correctness:
wager_amountis typed asint = None, which is an implicitOptional[int]and conflicts with Ruff’sRUF013.- If
is_wagerisTruebutwager_amountisNoneor< 25, we still persist an invite withis_wager = 'Yes'andwager_amount = 0, which will later causeDuelOperations.create_duelto 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” inget_duel_link_info
get_duel_link_infocatches a blanketExceptionand returnsNone, 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 ofNone, 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 inget_duel_endpointlikely never matches duelsOn 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_dueland the frontend) the identifier field is consistently namedduelId, notid. 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 opportunitiesThe new link-generation flow is wired coherently:
handleGenerateLinkvalidates difficulty and wager constraints, checks current XP fromleaderboard, and callsgenerateDuelLinkwith normalized usernames.generatedLink/generatingLinkstate drives the UI, with difficulty changes clearing stale links in both Normal and Wager tabs.handleCopyLinkusesnavigator.clipboard.writeTextand surfaces success/failure via notifications anderror.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
generatedLinkis shared between tabs, you might want to clear it when switchingmainTabas 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis 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)
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)
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
isReversedstate remainsfalse. This creates a mismatch between the actual sort order and the state variable, which affects the button label accuracy.Consider initializing
isReversed = trueon 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_invitefunction correctly mirrors the structure ofsend_email_otpwith 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
xpfield 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-otpendpoint.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
navigateToStepprop to navigate to the learn section.src/components/LeaderboardStep.jsx (1)
26-26: LGTM! Navigation prop correctly passed through to header.The
navigateToStepprop is properly added to the component signature and passed through toLeaderboardHeader, 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
CodeEditorcomponent 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 operationsThe
GenerateDuelLinkRequest/AcceptDuelLinkRequestmodels and the/generate-duel-linkand/duel-link/{token}endpoints are wired cleanly intoDuelInviteLinkOperations, with appropriate auth on generation and a public read endpoint for consumption.Once the
get_duel_link_infoerror handling is tightened inaws.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 appropriatelyThe new header controls and conditional rendering for “Group Members” vs “Anyone” mode are well thought out:
- Toggling modes resets
searchUsername,searchResult,inviteEmail, andselectedFriendto 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
SearchableDropdownfor 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 | |||
There was a problem hiding this comment.
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 | |||
There was a problem hiding this comment.
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.
| @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() | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| @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 | |||
There was a problem hiding this comment.
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.
| class AcceptDuelLinkRequest(BaseModel): | ||
| token: str | ||
| accepting_username: str | ||
| opponent_wager: int = None # For wager duels | ||
|
|
There was a problem hiding this comment.
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:
- Potential crash when
difficultyis 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- 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
| // Reverse sort button | ||
| const reverseSortBtn = document.getElementById('reverseSortBtn'); | ||
| reverseSortBtn.addEventListener('click', () => { | ||
| isReversed = !isReversed; | ||
| allLogs.reverse(); | ||
| reverseSortBtn.textContent = isReversed ? '⬇️ Latest First' : '⬆️ Oldest First'; | ||
| filterLogs(); | ||
| }); |
There was a problem hiding this comment.
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.
| // 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.
| quickActionsProps={{ handleLogout }} | ||
| /> | ||
| )} | ||
| {step === 'learn' && <LearnStep {...stepProps} />} |
There was a problem hiding this comment.
🧩 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.
| // 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); | ||
| } | ||
| }; |
There was a problem hiding this comment.
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:
duelModeis toggled only in the Normal tab.handleSendChallengebranches solely onduelMode:
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
| } 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; |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
| 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.
|
Closing - PR contained too many unrelated changes. Creating focused PR with only log viewer changes. |
Summary
fastapi-dev.logfor dev,fastapi.logfor prodChanges
Log Viewer Improvements
Environment-Aware Log Routing
Testing
fastapi-dev.logwhen PORT=42069fastapi.logwhen PORT=6969🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes & Improvements