Spaces:
Sleeping
Sleeping
| 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"]) | |
| 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 [] | |
| 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 [] | |
| 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 | |
| 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, | |
| } | |
| 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 [] | |