codenexus / routers /challenges.py
farhan1221's picture
Upload 26 files
91990f9 verified
Raw
History Blame Contribute Delete
9.47 kB
from fastapi import APIRouter, HTTPException, Request, Depends
from config.database import get_supabase_admin
from middleware.auth_guard import get_current_user, get_current_user_optional
from models.challenge import ChallengeSubmit
from services.ai_router import route_analysis
from services.code_runner import run_code
router = APIRouter(prefix="/api/challenges", tags=["challenges"])
@router.get("/all")
async def get_challenges(difficulty: str = "", category: str = "", company: str = ""):
db = get_supabase_admin()
query = db.table("challenges").select("*").eq("status", "published")
if difficulty:
query = query.eq("difficulty", difficulty)
if category:
query = query.eq("category", category)
if company:
query = query.eq("company_tag", company)
result = query.order("created_at", desc=True).execute()
return result.data or []
@router.get("/interview")
async def get_interview_questions(company: str = ""):
db = get_supabase_admin()
query = db.table("challenges").select("*").eq("status", "published").eq("is_interview_question", True)
if company:
query = query.eq("company_tag", company)
result = query.execute()
return result.data or []
@router.get("/{challenge_id}")
async def get_challenge(challenge_id: str):
db = get_supabase_admin()
result = db.table("challenges").select("*").eq("id", challenge_id).eq("status", "published").single().execute()
if not result.data:
raise HTTPException(status_code=404, detail="Challenge not found")
return result.data
@router.post("/{challenge_id}/submit")
async def submit_challenge(challenge_id: str, data: ChallengeSubmit, request: Request):
user = await get_current_user_optional(request)
db = get_supabase_admin()
# Get challenge
ch = db.table("challenges").select("*").eq("id", challenge_id).single().execute()
if not ch.data:
raise HTTPException(status_code=404, detail="Challenge not found")
challenge = ch.data
# Scenario 1: MCQ Challenge (Quiz or Single)
if challenge.get('type') == 'mcq':
questions = challenge.get('questions', [])
# Multi-question Quiz Logic
if questions and len(questions) > 0:
if data.selected_options is None or len(data.selected_options) != len(questions):
raise HTTPException(status_code=400, detail=f"selected_options array is required and must match questions length ({len(questions)})")
results = []
correct_count = 0
for i, q in enumerate(questions):
sel = data.selected_options[i]
# Flexible key for backward compatibility or different bulk upload formats
cor = q.get('correct_index')
if cor is None:
cor = q.get('correct_option', 0)
is_correct = sel == cor
if is_correct:
correct_count += 1
results.append({
"question_index": i,
"selected": sel,
"correct": cor,
"passed": is_correct,
"explanation": q.get('explanation', "No explanation provided.")
})
score = round((correct_count / len(questions)) * 100, 2)
passed = correct_count == len(questions)
ai_feedback = {
"feedback": f"You scored {correct_count}/{len(questions)} ({score}%). " +
("Perfect! You've mastered this topic." if passed else "Good effort! Review the explanations below to improve."),
"quiz_results": results,
"score": score,
"time_complexity": "N/A",
"space_complexity": "N/A",
}
if user:
try:
# Final sanity check on fields
insert_data = {
"user_id": str(user.id),
"challenge_id": challenge_id,
"challenge_type": "mcq",
"passed": passed,
"ai_feedback": ai_feedback or {},
"metadata": {
"score": score,
"selected_options": data.selected_options,
"quiz_length": len(questions)
}
}
db.table("challenge_submissions").insert(insert_data).execute()
except Exception as db_err:
import logging
logger = logging.getLogger(__name__)
logger.error(f"Failed to save MCQ submission: {str(db_err)}")
return {
"passed": passed,
"score": score,
"ai_feedback": ai_feedback,
}
# Single Question MCQ Logic (Backward compatibility)
if data.selected_option is None:
raise HTTPException(status_code=400, detail="selected_option is required for single-question MCQ")
correct_idx = challenge.get('correct_option', 0)
passed = data.selected_option == correct_idx
ai_feedback = {
"feedback": "Correct! You have a good understanding of this concept." if passed else "Incorrect. Review the concept and try again.",
"correct_approach": f"The correct answer is option index {correct_idx}.",
"time_complexity": "N/A",
"space_complexity": "N/A",
"improvements": []
}
if user:
try:
db.table("challenge_submissions").insert({
"user_id": str(user.id),
"challenge_id": challenge_id,
"challenge_type": "mcq",
"selected_option": data.selected_option,
"passed": passed,
"ai_feedback": ai_feedback,
"metadata": {"score": 100 if passed else 0}
}).execute()
except Exception as db_err:
import logging
logger = logging.getLogger(__name__)
logger.error(f"Failed to save Single MCQ submission: {str(db_err)}")
return {
"passed": passed,
"correct_option": correct_idx,
"ai_feedback": ai_feedback,
}
# Scenario 2: Coding Challenge
if not data.code:
raise HTTPException(status_code=400, detail="code is required for coding challenges")
try:
output = await run_code(data.language, data.code)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
# Check test cases
test_cases = challenge.get("test_cases", [])
passed = True
test_results = []
for tc in test_cases:
expected = str(tc.get("output", "")).strip()
actual = output.strip()
ok = expected in actual
test_results.append({"input": tc.get("input"), "expected": expected, "actual": actual, "passed": ok})
if not ok:
passed = False
# Get AI feedback
from services.settings import is_ai_enabled
if is_ai_enabled():
prompt = f"""A user submitted this {data.language} code for a coding challenge.
Challenge: {challenge.get('title')}
Description: {challenge.get('description')}
Code:
{data.code}
Output: {output}
Tests passed: {passed}
Respond in JSON only:
{{
"feedback": "2-3 sentence explanation of their solution quality",
"correct_approach": "brief explanation of the optimal approach",
"time_complexity": "O(?)",
"space_complexity": "O(?)",
"improvements": ["improvement 1", "improvement 2"]
}}"""
try:
ai_feedback = await route_analysis(prompt)
except Exception:
ai_feedback = {"feedback": "Good attempt! Keep practicing.", "correct_approach": "", "improvements": []}
else:
ai_feedback = {
"feedback": "Submission processed. Detailed AI analysis is currently disabled by administrator.",
"correct_approach": "AI analysis is required to generate the optimal approach.",
"time_complexity": "N/A",
"space_complexity": "N/A",
"improvements": []
}
# Save submission if logged in
if user:
try:
db.table("challenge_submissions").insert({
"user_id": str(user.id),
"challenge_id": challenge_id,
"challenge_type": "coding",
"code": data.code,
"language": data.language,
"passed": passed,
"ai_feedback": ai_feedback,
}).execute()
except Exception:
pass
return {
"passed": passed,
"output": output,
"test_results": test_results,
"ai_feedback": ai_feedback,
}
@router.get("/{challenge_id}/submissions")
async def get_my_submissions(challenge_id: str, request: Request):
user = await get_current_user(request)
db = get_supabase_admin()
result = db.table("challenge_submissions") \
.select("*") \
.eq("user_id", str(user.id)) \
.eq("challenge_id", challenge_id) \
.order("submitted_at", desc=True) \
.execute()
return result.data or []