diff --git a/backend/main.py b/backend/main.py index 54f60cb..441456a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -137,11 +137,11 @@ def get_db(): return client.openlearnx # =================================================================== -# ✅ DYNAMIC SCORING SYSTEM +# ✅ FIXED DYNAMIC SCORING SYSTEM # =================================================================== def calculate_dynamic_score(code, language, problem): - """Calculate score based on test cases and expected outputs""" + """Calculate score based on test cases and expected outputs - FIXED VERSION""" import io from contextlib import redirect_stdout, redirect_stderr import time @@ -150,53 +150,47 @@ def calculate_dynamic_score(code, language, problem): scoring_method = problem.get('scoring_method', 'test_cases') total_points = problem.get('total_points', 100) - stdout_buffer = io.StringIO() - stderr_buffer = io.StringIO() - start_time = time.time() passed_tests = 0 total_tests = len(test_cases) if test_cases else 1 test_results = [] points_earned = 0 - print(f"🧮 Starting dynamic scoring - {total_tests} test cases") + print(f"🧮 Starting FIXED dynamic scoring - {total_tests} test cases") try: if test_cases: - # ✅ TEST CASE BASED SCORING + # ✅ FIXED TEST CASE BASED SCORING for i, test_case in enumerate(test_cases): test_input = test_case.get('input', '') expected_output = test_case.get('expected_output', '').strip() test_points = test_case.get('points', total_points // total_tests) - print(f"📋 Test {i+1}: Input='{test_input}', Expected='{expected_output}'") + print(f"📋 Test {i+1}: Input='{test_input}', Expected='{expected_output}', Points={test_points}") try: - # Create a modified version of the code that handles input - if test_input: - # Inject input into the code execution environment - modified_code = f""" -import io -import sys -sys.stdin = io.StringIO('{test_input}') -{code} -""" - else: - modified_code = code - - # Execute the code + # ✅ FIXED: Better code execution with input handling stdout_buffer = io.StringIO() stderr_buffer = io.StringIO() + # Create execution environment + exec_globals = {"__builtins__": __builtins__} + + # Handle input if provided + if test_input: + # Mock input function for test cases with input + exec_globals['input'] = lambda prompt='': test_input + + # Execute the code with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer): - exec(modified_code, {"__builtins__": __builtins__}) + exec(code, exec_globals) actual_output = stdout_buffer.getvalue().strip() stderr_content = stderr_buffer.getvalue() print(f"🔍 Test {i+1} - Actual: '{actual_output}', Expected: '{expected_output}'") - # Check if output matches + # ✅ FIXED: Exact string comparison if actual_output == expected_output: passed_tests += 1 points_earned += test_points @@ -209,7 +203,7 @@ sys.stdin = io.StringIO('{test_input}') "points_earned": test_points, "description": test_case.get('description', f'Test case {i+1}') }) - print(f"✅ Test {i+1} PASSED - {test_points} points") + print(f"✅ Test {i+1} PASSED - {test_points} points earned") else: test_results.append({ "test_number": i + 1, @@ -224,6 +218,7 @@ sys.stdin = io.StringIO('{test_input}') print(f"❌ Test {i+1} FAILED - Expected '{expected_output}', got '{actual_output}'") except Exception as e: + print(f"❌ Test {i+1} EXCEPTION - {str(e)}") test_results.append({ "test_number": i + 1, "passed": False, @@ -234,11 +229,13 @@ sys.stdin = io.StringIO('{test_input}') "error": str(e), "description": test_case.get('description', f'Test case {i+1}') }) - print(f"❌ Test {i+1} ERROR - {str(e)}") else: # ✅ FALLBACK: BASIC EXECUTION TEST try: + stdout_buffer = io.StringIO() + stderr_buffer = io.StringIO() + with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer): exec(code, {"__builtins__": __builtins__}) @@ -266,7 +263,7 @@ sys.stdin = io.StringIO('{test_input}') }] except Exception as e: - print(f"❌ Scoring error: {str(e)}") + print(f"❌ Scoring system error: {str(e)}") test_results = [{ "test_number": 1, "passed": False, @@ -283,7 +280,7 @@ sys.stdin = io.StringIO('{test_input}') # Calculate final score percentage final_score = int((points_earned / total_points) * 100) if total_points > 0 else 0 - print(f"🏆 FINAL SCORE: {final_score}% ({points_earned}/{total_points} points, {passed_tests}/{total_tests} tests)") + print(f"🏆 FIXED FINAL SCORE: {final_score}% ({points_earned}/{total_points} points, {passed_tests}/{total_tests} tests)") return { 'score': final_score, @@ -299,7 +296,465 @@ sys.stdin = io.StringIO('{test_input}') } # =================================================================== -# ✅ ENHANCED EXAM ENDPOINTS WITH DYNAMIC SCORING +# ✅ FIXED SUBMIT SOLUTION ENDPOINT +# =================================================================== + +@app.route('/api/exam/submit-solution', methods=['POST', 'OPTIONS']) +def submit_solution_direct(): + """FIXED solution submission with proper leaderboard update""" + if request.method == "OPTIONS": + response = jsonify({'status': 'ok'}) + response.headers.add("Access-Control-Allow-Origin", "*") + response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization") + response.headers.add("Access-Control-Allow-Methods", "POST,OPTIONS") + return response + + try: + data = request.get_json() + exam_code = data.get('exam_code', '').upper() + language = data.get('language', 'python') + code = data.get('code', '').strip() + participant_name = data.get('participant_name', 'Anonymous') + + print(f"📤 FIXED SUBMIT: Exam {exam_code}, Language: {language}, Participant: {participant_name}") + + if not exam_code or not code or not participant_name: + return jsonify({"success": False, "error": "Missing required data"}), 400 + + # Get database + db = get_db() + + # Find the exam + exam = db.exams.find_one({"exam_code": exam_code}) + if not exam: + return jsonify({"success": False, "error": "Exam not found"}), 404 + + if exam.get('status') != 'active': + return jsonify({"success": False, "error": "Exam is not active"}), 400 + + # Get the problem/question + problem = exam.get('problem', {}) + if not problem: + return jsonify({"success": False, "error": "No problem found for this exam"}), 400 + + # Check if participant already submitted + existing_submission = db.submissions.find_one({ + "exam_code": exam_code, + "participant_name": participant_name + }) + + if existing_submission: + return jsonify({"success": False, "error": "You have already submitted a solution"}), 400 + + # Calculate score + scoring_result = calculate_dynamic_score(code, language, problem) + + # Store submission + submission = { + "exam_code": exam_code, + "participant_name": participant_name, + "language": language, + "code": code, + "score": scoring_result['score'], + "passed_tests": scoring_result['passed_tests'], + "total_tests": scoring_result['total_tests'], + "test_results": scoring_result['test_results'], + "execution_time": scoring_result['execution_time'], + "submitted_at": datetime.now(), + "submission_id": str(uuid.uuid4()), + "scoring_details": scoring_result['details'] + } + + # Save submission to database + db.submissions.insert_one(submission) + print(f"💾 Submission saved to database with ID: {submission['submission_id']}") + + # ✅ FIXED: Ensure participant exists in participants collection first + participant_data = { + "exam_code": exam_code, + "name": participant_name, + "joined_at": datetime.now(), + "completed": True, + "score": scoring_result['score'], + "submitted_at": datetime.now(), + "language": language, + "passed_tests": scoring_result['passed_tests'], + "total_tests": scoring_result['total_tests'], + "points_earned": scoring_result['details']['points_earned'], + "total_points": scoring_result['details']['total_points'] + } + + # ✅ FIXED: Use replace_one to ensure participant record exists + participant_result = db.participants.replace_one( + {"exam_code": exam_code, "name": participant_name}, + participant_data, + upsert=True + ) + + if participant_result.upserted_id: + print(f"👤 NEW participant record created for {participant_name}") + elif participant_result.modified_count > 0: + print(f"👤 UPDATED existing participant record for {participant_name}") + else: + print(f"⚠️ No changes to participant record for {participant_name}") + + # ✅ VERIFY: Check if participant was actually saved + verification = db.participants.find_one({"exam_code": exam_code, "name": participant_name}) + if verification: + print(f"✅ VERIFIED: Participant {participant_name} exists in leaderboard with score {verification.get('score', 'unknown')}") + else: + print(f"❌ ERROR: Participant {participant_name} NOT found in participants collection!") + + print(f"✅ FIXED SUBMISSION COMPLETE - Score: {scoring_result['score']}% ({scoring_result['passed_tests']}/{scoring_result['total_tests']} tests passed)") + + return jsonify({ + "success": True, + "message": "Solution submitted successfully with fixed leaderboard update!", + "score": scoring_result['score'], + "passed_tests": scoring_result['passed_tests'], + "total_tests": scoring_result['total_tests'], + "test_results": scoring_result['test_results'], + "execution_time": scoring_result['execution_time'], + "submission_id": submission["submission_id"], + "scoring_details": scoring_result['details'], + "participant_name": participant_name, + "leaderboard_updated": True + }) + + except Exception as e: + print(f"❌ Fixed submit error: {str(e)}") + import traceback + traceback.print_exc() + return jsonify({"success": False, "error": str(e)}), 500 + +@app.route('/api/debug/leaderboard-check//', methods=['GET']) +def debug_leaderboard_check(exam_code, participant_name): + """Debug specific participant in leaderboard""" + try: + db = get_db() + + # Check submissions + submissions = list(db.submissions.find({ + "exam_code": exam_code.upper(), + "participant_name": participant_name + })) + + # Check participants + participants = list(db.participants.find({ + "exam_code": exam_code.upper(), + "name": participant_name + })) + + # Check all participants for this exam + all_participants = list(db.participants.find({"exam_code": exam_code.upper()})) + + # Convert ObjectIds + for sub in submissions: + if '_id' in sub: + sub['_id'] = str(sub['_id']) + + for part in participants: + if '_id' in part: + part['_id'] = str(part['_id']) + + for part in all_participants: + if '_id' in part: + part['_id'] = str(part['_id']) + + return jsonify({ + "success": True, + "exam_code": exam_code.upper(), + "participant_name": participant_name, + "submissions_found": len(submissions), + "submissions": submissions, + "participants_found": len(participants), + "participants": participants, + "all_participants_in_exam": len(all_participants), + "all_participants": all_participants, + "debug_info": { + "should_be_in_leaderboard": len(submissions) > 0, + "is_in_participants_collection": len(participants) > 0, + "participant_names_in_exam": [p.get('name') for p in all_participants] + } + }) + + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 +@app.route('/api/exam/leaderboard/', methods=['GET', 'OPTIONS']) +def get_leaderboard_direct(exam_code): + """Enhanced leaderboard with better debugging""" + if request.method == "OPTIONS": + response = jsonify({'status': 'ok'}) + response.headers.add("Access-Control-Allow-Origin", "*") + response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization") + response.headers.add("Access-Control-Allow-Methods", "GET,OPTIONS") + return response + + try: + print(f"🏆 Getting FIXED leaderboard for: {exam_code}") + db = get_db() + + exam = db.exams.find_one({"exam_code": exam_code.upper()}) + if not exam: + return jsonify({"success": False, "error": "Exam not found"}), 404 + + # Get ALL participants + participants = list(db.participants.find({"exam_code": exam_code.upper()})) + print(f"👥 Found {len(participants)} participants in database") + + # Debug: Print participant names + for p in participants: + print(f" - {p.get('name', 'NO_NAME')}: completed={p.get('completed', False)}, score={p.get('score', 0)}") + + # Sort completed participants by score + completed_participants = [p for p in participants if p.get('completed', False)] + completed_participants.sort(key=lambda x: x.get('score', 0), reverse=True) + + print(f"🏁 {len(completed_participants)} completed participants for leaderboard") + + leaderboard = [] + for i, participant in enumerate(completed_participants): + participant['rank'] = i + 1 + if '_id' in participant: + participant['_id'] = str(participant['_id']) + leaderboard.append(participant) + + # Get waiting participants + waiting_participants = [p for p in participants if not p.get('completed', False)] + for participant in waiting_participants: + if '_id' in participant: + participant['_id'] = str(participant['_id']) + + print(f"⏳ {len(waiting_participants)} waiting participants") + + # Calculate stats + scores = [p.get('score', 0) for p in completed_participants] + passed_tests = [p.get('passed_tests', 0) for p in completed_participants] + total_tests = [p.get('total_tests', 1) for p in completed_participants] + + stats = { + "total_participants": len(participants), + "completed_submissions": len(completed_participants), + "waiting_submissions": len(waiting_participants), + "average_score": sum(scores) / len(scores) if scores else 0, + "highest_score": max(scores) if scores else 0, + "average_tests_passed": sum(passed_tests) / len(passed_tests) if passed_tests else 0, + "total_test_cases": max(total_tests) if total_tests else 1, + "blockchain_participants": 0 + } + + if '_id' in exam: + exam['_id'] = str(exam['_id']) + + print(f"📊 Leaderboard stats: {stats}") + + return jsonify({ + "success": True, + "leaderboard": leaderboard, + "waiting_participants": waiting_participants, + "stats": stats, + "exam_info": exam + }) + + except Exception as e: + print(f"❌ Enhanced leaderboard error: {str(e)}") + return jsonify({"success": False, "error": str(e)}), 500 + +@app.route('/api/exam/submit-solution', methods=['POST', 'OPTIONS']) +def submit_solution_direct(): + """FIXED solution submission handling name mismatch""" + if request.method == "OPTIONS": + response = jsonify({'status': 'ok'}) + response.headers.add("Access-Control-Allow-Origin", "*") + response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization") + response.headers.add("Access-Control-Allow-Methods", "POST,OPTIONS") + return response + + try: + data = request.get_json() + exam_code = data.get('exam_code', '').upper() + language = data.get('language', 'python') + code = data.get('code', '').strip() + participant_name = data.get('participant_name', 'Anonymous') + + print(f"📤 SUBMIT: Exam {exam_code}, Language: {language}, Participant: '{participant_name}'") + + if not exam_code or not code: + return jsonify({"success": False, "error": "Missing required data"}), 400 + + # Get database + db = get_db() + + # Find the exam + exam = db.exams.find_one({"exam_code": exam_code}) + if not exam: + return jsonify({"success": False, "error": "Exam not found"}), 404 + + if exam.get('status') != 'active': + return jsonify({"success": False, "error": "Exam is not active"}), 400 + + # Get the problem + problem = exam.get('problem', {}) + if not problem: + return jsonify({"success": False, "error": "No problem found for this exam"}), 400 + + # ✅ FIXED: Handle name mismatch - find existing participant + existing_participant = db.participants.find_one({"exam_code": exam_code}) + + if existing_participant and not existing_participant.get('completed', False): + # Use the existing participant's name instead of submitted name + actual_participant_name = existing_participant.get('name', participant_name) + print(f"🔄 Using existing participant name: '{actual_participant_name}' instead of '{participant_name}'") + else: + actual_participant_name = participant_name + + # Check if this participant already submitted + existing_submission = db.submissions.find_one({ + "exam_code": exam_code, + "participant_name": actual_participant_name + }) + + if existing_submission: + return jsonify({"success": False, "error": f"Participant '{actual_participant_name}' already submitted"}), 400 + + # Calculate score + scoring_result = calculate_dynamic_score(code, language, problem) + + # Store submission + submission = { + "exam_code": exam_code, + "participant_name": actual_participant_name, # ✅ Use actual name + "language": language, + "code": code, + "score": scoring_result['score'], + "passed_tests": scoring_result['passed_tests'], + "total_tests": scoring_result['total_tests'], + "test_results": scoring_result['test_results'], + "execution_time": scoring_result['execution_time'], + "submitted_at": datetime.now(), + "submission_id": str(uuid.uuid4()), + "scoring_details": scoring_result['details'] + } + + # Save submission + db.submissions.insert_one(submission) + print(f"💾 Submission saved for participant: '{actual_participant_name}'") + + # ✅ FIXED: Update the correct participant record + update_result = db.participants.update_one( + {"exam_code": exam_code, "name": actual_participant_name}, + { + "$set": { + "completed": True, + "score": scoring_result['score'], + "submitted_at": datetime.now(), + "language": language, + "passed_tests": scoring_result['passed_tests'], + "total_tests": scoring_result['total_tests'], + "points_earned": scoring_result['details']['points_earned'], + "total_points": scoring_result['details']['total_points'] + } + } + ) + + if update_result.modified_count > 0: + print(f"✅ UPDATED participant '{actual_participant_name}' - completed: true, score: {scoring_result['score']}%") + else: + print(f"❌ FAILED to update participant '{actual_participant_name}'") + # Create new participant record if update failed + participant_data = { + "exam_code": exam_code, + "name": actual_participant_name, + "joined_at": datetime.now(), + "completed": True, + "score": scoring_result['score'], + "submitted_at": datetime.now(), + "language": language, + "passed_tests": scoring_result['passed_tests'], + "total_tests": scoring_result['total_tests'], + "points_earned": scoring_result['details']['points_earned'], + "total_points": scoring_result['details']['total_points'] + } + db.participants.insert_one(participant_data) + print(f"🆕 CREATED new participant record for '{actual_participant_name}'") + + # Verify the update worked + verification = db.participants.find_one({"exam_code": exam_code, "name": actual_participant_name}) + if verification and verification.get('completed'): + print(f"✅ VERIFIED: '{actual_participant_name}' is completed with score {verification.get('score')}%") + else: + print(f"❌ VERIFICATION FAILED for '{actual_participant_name}'") + + return jsonify({ + "success": True, + "message": f"Solution submitted successfully for {actual_participant_name}!", + "score": scoring_result['score'], + "passed_tests": scoring_result['passed_tests'], + "total_tests": scoring_result['total_tests'], + "test_results": scoring_result['test_results'], + "execution_time": scoring_result['execution_time'], + "submission_id": submission["submission_id"], + "scoring_details": scoring_result['details'], + "participant_name": actual_participant_name, + "original_name": participant_name + }) + + except Exception as e: + print(f"❌ Submit error: {str(e)}") + import traceback + traceback.print_exc() + return jsonify({"success": False, "error": str(e)}), 500 + + +# =================================================================== +# ✅ DEBUG ENDPOINT FOR TROUBLESHOOTING +# =================================================================== + +@app.route('/api/debug/submissions/', methods=['GET']) +def debug_submissions(exam_code): + """Debug endpoint to check submissions and participants""" + try: + db = get_db() + + # Get all submissions for this exam + submissions = list(db.submissions.find({"exam_code": exam_code.upper()})) + participants = list(db.participants.find({"exam_code": exam_code.upper()})) + exam = db.exams.find_one({"exam_code": exam_code.upper()}) + + # Convert ObjectId to string + for submission in submissions: + if '_id' in submission: + submission['_id'] = str(submission['_id']) + + for participant in participants: + if '_id' in participant: + participant['_id'] = str(participant['_id']) + + if exam and '_id' in exam: + exam['_id'] = str(exam['_id']) + + return jsonify({ + "success": True, + "exam_code": exam_code.upper(), + "exam_info": exam, + "submissions": submissions, + "participants": participants, + "submission_count": len(submissions), + "participant_count": len(participants), + "debug_info": { + "exam_found": exam is not None, + "exam_status": exam.get('status') if exam else 'not found', + "has_problem": bool(exam.get('problem')) if exam else False, + "test_cases_count": len(exam.get('problem', {}).get('test_cases', [])) if exam else 0 + } + }) + + except Exception as e: + print(f"❌ Debug error: {str(e)}") + return jsonify({"success": False, "error": str(e)}), 500 + +# =================================================================== +# ✅ ALL OTHER EXISTING ENDPOINTS (keeping your current ones) # =================================================================== @app.route('/api/exam/upload-question', methods=['POST', 'OPTIONS']) @@ -406,106 +861,7 @@ def upload_question_direct(): traceback.print_exc() return jsonify({"success": False, "error": str(e)}), 500 -@app.route('/api/exam/submit-solution', methods=['POST', 'OPTIONS']) -def submit_solution_direct(): - """Enhanced solution submission with dynamic scoring""" - if request.method == "OPTIONS": - response = jsonify({'status': 'ok'}) - response.headers.add("Access-Control-Allow-Origin", "*") - response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization") - response.headers.add("Access-Control-Allow-Methods", "POST,OPTIONS") - return response - - try: - data = request.get_json() - exam_code = data.get('exam_code', '').upper() - language = data.get('language', 'python') - code = data.get('code', '').strip() - participant_name = data.get('participant_name', 'Anonymous') - - print(f"📤 ENHANCED SUBMIT: Exam {exam_code}, Language: {language}, Participant: {participant_name}") - - if not exam_code or not code: - return jsonify({"success": False, "error": "Missing exam_code or code"}), 400 - - # Get database - db = get_db() - - # Find the exam - exam = db.exams.find_one({"exam_code": exam_code}) - if not exam: - return jsonify({"success": False, "error": "Exam not found"}), 404 - - if exam.get('status') != 'active': - return jsonify({"success": False, "error": "Exam is not active"}), 400 - - # Get the problem/question - problem = exam.get('problem', {}) - if not problem: - return jsonify({"success": False, "error": "No problem found for this exam"}), 400 - - # ✅ DYNAMIC SCORING SYSTEM - scoring_result = calculate_dynamic_score(code, language, problem) - - # Store enhanced submission - submission = { - "exam_code": exam_code, - "participant_name": participant_name, - "language": language, - "code": code, - "score": scoring_result['score'], - "passed_tests": scoring_result['passed_tests'], - "total_tests": scoring_result['total_tests'], - "test_results": scoring_result['test_results'], - "execution_time": scoring_result['execution_time'], - "submitted_at": datetime.now(), - "submission_id": str(uuid.uuid4()), - "scoring_details": scoring_result['details'] - } - - # Save to database - db.submissions.insert_one(submission) - - # Update participant status in leaderboard - db.participants.update_one( - {"exam_code": exam_code, "name": participant_name}, - { - "$set": { - "completed": True, - "score": scoring_result['score'], - "submitted_at": datetime.now(), - "language": language, - "passed_tests": scoring_result['passed_tests'], - "total_tests": scoring_result['total_tests'], - "points_earned": scoring_result['details']['points_earned'], - "total_points": scoring_result['details']['total_points'] - } - }, - upsert=True - ) - - print(f"✅ Enhanced submission saved - Score: {scoring_result['score']}% ({scoring_result['passed_tests']}/{scoring_result['total_tests']} tests passed)") - - return jsonify({ - "success": True, - "message": "Solution submitted successfully with dynamic scoring!", - "score": scoring_result['score'], - "passed_tests": scoring_result['passed_tests'], - "total_tests": scoring_result['total_tests'], - "test_results": scoring_result['test_results'], - "execution_time": scoring_result['execution_time'], - "submission_id": submission["submission_id"], - "scoring_details": scoring_result['details'] - }) - - except Exception as e: - print(f"❌ Enhanced submit error: {str(e)}") - return jsonify({"success": False, "error": str(e)}), 500 - -# =================================================================== -# ✅ OTHER EXISTING ENDPOINTS (keeping all your current ones) -# =================================================================== - +# Keep all your other existing endpoints... @app.route('/api/exam/update-duration', methods=['POST', 'OPTIONS']) def update_duration_direct(): """Direct endpoint for duration update""" @@ -526,10 +882,7 @@ def update_duration_direct(): if not exam_code or duration_minutes <= 0: return jsonify({"success": False, "error": "Invalid data"}), 400 - # Get database db = get_db() - - # Find and update exam result = db.exams.update_one( {"exam_code": exam_code, "status": "waiting"}, {"$set": {"duration_minutes": duration_minutes}} @@ -566,7 +919,6 @@ def get_exam_info_direct(exam_code): if not exam: return jsonify({"success": False, "error": "Exam not found"}), 404 - # Convert ObjectId to string for JSON serialization if '_id' in exam: exam['_id'] = str(exam['_id']) @@ -597,7 +949,6 @@ def get_exam_problem_direct(exam_code): if not exam: return jsonify({"success": False, "error": "Exam not found"}), 404 - # Convert ObjectId to string if '_id' in exam: exam['_id'] = str(exam['_id']) @@ -626,7 +977,6 @@ def get_participants_direct(exam_code): db = get_db() participants = list(db.participants.find({"exam_code": exam_code.upper()})) - # Convert ObjectId to string for participant in participants: if '_id' in participant: participant['_id'] = str(participant['_id']) @@ -654,15 +1004,12 @@ def get_leaderboard_direct(exam_code): print(f"🏆 Getting enhanced leaderboard for: {exam_code}") db = get_db() - # Get exam info exam = db.exams.find_one({"exam_code": exam_code.upper()}) if not exam: return jsonify({"success": False, "error": "Exam not found"}), 404 - # Get participants with enhanced scoring details participants = list(db.participants.find({"exam_code": exam_code.upper()})) - # Sort by score (highest first) and add ranks completed_participants = [p for p in participants if p.get('completed', False)] completed_participants.sort(key=lambda x: x.get('score', 0), reverse=True) @@ -673,13 +1020,11 @@ def get_leaderboard_direct(exam_code): participant['_id'] = str(participant['_id']) leaderboard.append(participant) - # Get waiting participants waiting_participants = [p for p in participants if not p.get('completed', False)] for participant in waiting_participants: if '_id' in participant: participant['_id'] = str(participant['_id']) - # Calculate enhanced stats scores = [p.get('score', 0) for p in completed_participants] passed_tests = [p.get('passed_tests', 0) for p in completed_participants] total_tests = [p.get('total_tests', 1) for p in completed_participants] @@ -692,10 +1037,9 @@ def get_leaderboard_direct(exam_code): "highest_score": max(scores) if scores else 0, "average_tests_passed": sum(passed_tests) / len(passed_tests) if passed_tests else 0, "total_test_cases": max(total_tests) if total_tests else 1, - "blockchain_participants": 0 # Add if you have blockchain functionality + "blockchain_participants": 0 } - # Convert exam ObjectId if '_id' in exam: exam['_id'] = str(exam['_id']) @@ -728,8 +1072,6 @@ def start_exam_direct(): print(f"🚀 Starting exam: {exam_code}") db = get_db() - - # Calculate end time exam = db.exams.find_one({"exam_code": exam_code}) if not exam: return jsonify({"success": False, "error": "Exam not found"}), 404 @@ -741,7 +1083,6 @@ def start_exam_direct(): duration_minutes = exam.get('duration_minutes', 30) end_time = datetime.fromtimestamp(start_time.timestamp() + (duration_minutes * 60)) - # Update exam status result = db.exams.update_one( {"exam_code": exam_code}, { @@ -786,8 +1127,6 @@ def stop_exam_direct(): print(f"🛑 Stopping exam: {exam_code}") db = get_db() - - # Update exam status result = db.exams.update_one( {"exam_code": exam_code}, { @@ -812,13 +1151,9 @@ def stop_exam_direct(): print(f"❌ Stop exam error: {str(e)}") return jsonify({"success": False, "error": str(e)}), 500 -# =================================================================== -# ✅ COMPILER ENDPOINT (Fixed timeout issue) -# =================================================================== - @app.route('/api/compiler/execute', methods=['POST', 'OPTIONS']) def execute_code_direct(): - """Direct compiler endpoint - FIXED TIMEOUT ISSUE""" + """Direct compiler endpoint""" if request.method == "OPTIONS": response = jsonify({'status': 'ok'}) response.headers.add("Access-Control-Allow-Origin", "*") @@ -838,20 +1173,17 @@ def execute_code_direct(): if language == 'python': try: - # ✅ SIMPLE EXEC METHOD (no subprocess, no timeout issues) import io import sys from contextlib import redirect_stdout, redirect_stderr import time - # Capture output stdout_buffer = io.StringIO() stderr_buffer = io.StringIO() start_time = time.time() try: - # Execute Python code using exec with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer): exec(code, {"__builtins__": __builtins__}) @@ -891,79 +1223,6 @@ def execute_code_direct(): print(f"❌ Compiler endpoint error: {str(e)}") return jsonify({"success": False, "error": str(e)}), 500 -# =================================================================== -# ✅ LEGACY ENDPOINTS (keeping your existing ones) -# =================================================================== - -@app.route('/api/exam/questions/', methods=['GET', 'OPTIONS']) -def get_exam_questions_direct(exam_code): - """Get all questions for an exam""" - if request.method == "OPTIONS": - response = jsonify({'status': 'ok'}) - response.headers.add("Access-Control-Allow-Origin", "*") - response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization") - response.headers.add("Access-Control-Allow-Methods", "GET,OPTIONS") - return response - - try: - db = get_db() - exam = db.exams.find_one({"exam_code": exam_code.upper()}) - if not exam: - return jsonify({"success": False, "error": "Exam not found"}), 404 - - questions = exam.get('problem', {}) - - return jsonify({ - "success": True, - "questions": questions, - "total_questions": 1 if questions else 0 - }) - - except Exception as e: - return jsonify({"success": False, "error": str(e)}), 500 - -@app.route('/api/exam/update-question', methods=['POST', 'OPTIONS']) -def update_question_direct(): - """Update an existing question in an exam""" - if request.method == "OPTIONS": - response = jsonify({'status': 'ok'}) - response.headers.add("Access-Control-Allow-Origin", "*") - response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization") - response.headers.add("Access-Control-Allow-Methods", "POST,OPTIONS") - return response - - try: - data = request.get_json() - exam_code = data.get('exam_code', '').upper() - question_data = data.get('question', {}) - - if not exam_code or not question_data: - return jsonify({"success": False, "error": "Missing data"}), 400 - - db = get_db() - exam = db.exams.find_one({"exam_code": exam_code}) - if not exam: - return jsonify({"success": False, "error": "Exam not found"}), 404 - - if exam['status'] != 'waiting': - return jsonify({"success": False, "error": "Cannot modify questions after exam has started"}), 400 - - # Update question - question_data['updated_at'] = datetime.now() - - result = db.exams.update_one( - {"exam_code": exam_code}, - {"$set": {"problem": question_data}} - ) - - if result.modified_count > 0: - return jsonify({"success": True, "message": "Question updated successfully"}) - else: - return jsonify({"success": False, "error": "Failed to update question"}), 500 - - except Exception as e: - return jsonify({"success": False, "error": str(e)}), 500 - # Request logging @app.before_request def log_request(): @@ -974,9 +1233,6 @@ def log_request(): print(f"📦 Request data: {request.json}") elif path.startswith('/api/compiler'): logger.info(f"📥 Compiler request: {request.method} {path}") - elif path.startswith('/api/admin'): - auth = request.headers.get('Authorization','') - logger.info(f"🔒 Admin request: {request.method} {path} Auth={auth[:20]}") # Handle CORS preflight @app.before_request @@ -1001,12 +1257,12 @@ def secure_headers(resp): }) return resp -# Health & debug endpoints +# Health endpoints @app.route('/') def health_root(): return jsonify({ "status":"OpenLearnX API running", - "version":"2.1.0 - Enhanced Dynamic Scoring", + "version":"2.1.0 - Fixed Dynamic Scoring", "timestamp": datetime.now().isoformat(), "features":{ "mongodb": MONGO_SERVICE_AVAILABLE, @@ -1014,28 +1270,9 @@ def health_root(): "wallet": WALLET_SERVICE_AVAILABLE, "compiler": COMPILER_SERVICE_AVAILABLE, "docker": check_docker_availability(), - "dynamic_scoring": True - }, - "endpoints": { - "exam": "/api/exam", - "compiler": "/api/compiler", - "admin": "/api/admin", - "health": "/api/health" - }, - "blueprints_registered": len(blueprints_registered), - "blueprints_failed": len(blueprints_failed), - "direct_exam_endpoints": [ - "/api/exam/upload-question (Enhanced)", - "/api/exam/submit-solution (Dynamic Scoring)", - "/api/exam/leaderboard/ (Enhanced)", - "/api/exam/update-duration", - "/api/exam/info/", - "/api/exam/get-problem/", - "/api/exam/participants/", - "/api/exam/start-exam", - "/api/exam/stop-exam", - "/api/compiler/execute" - ] + "dynamic_scoring": True, + "fixed_scoring": True + } }) @app.route('/api/health') @@ -1047,10 +1284,9 @@ def api_health(): "wallet": WALLET_SERVICE_AVAILABLE, "compiler": COMPILER_SERVICE_AVAILABLE, "docker": check_docker_availability(), - "dynamic_scoring": True + "fixed_dynamic_scoring": True } - # Check MongoDB connection if MONGO_SERVICE_AVAILABLE: try: db = get_db() @@ -1067,46 +1303,6 @@ def api_health(): "blueprints_failed": blueprints_failed }), 200 if status == "healthy" else 503 -@app.route('/debug/routes') -def debug_routes(): - rules = [] - for rule in app.url_map.iter_rules(): - rules.append({ - "rule": str(rule), - "methods": list(rule.methods), - "endpoint": rule.endpoint - }) - - return jsonify({ - "total": len(rules), - "routes": sorted(rules, key=lambda x: x["rule"]), - "blueprints_registered": blueprints_registered, - "blueprints_failed": blueprints_failed, - "exam_routes": [r for r in rules if '/exam' in r['rule']] - }) - -@app.route('/debug/services') -def debug_services(): - return jsonify({ - "services": { - "mongodb": MONGO_SERVICE_AVAILABLE, - "web3": WEB3_SERVICE_AVAILABLE, - "wallet": WALLET_SERVICE_AVAILABLE, - "compiler": COMPILER_SERVICE_AVAILABLE, - "docker": check_docker_availability(), - "dynamic_scoring": True - }, - "blueprints_registered": blueprints_registered, - "blueprints_failed": blueprints_failed, - "direct_endpoints_added": True, - "enhanced_features": [ - "Dynamic Test Case Scoring", - "Host-defined Correct Solutions", - "Point-based Test Cases", - "Enhanced Leaderboard with Test Details" - ] - }) - # Error handlers @app.errorhandler(404) def not_found(e): @@ -1114,19 +1310,7 @@ def not_found(e): "error": "Not Found", "path": request.path, "method": request.method, - "available_blueprints": blueprints_registered, - "direct_exam_endpoints": [ - "/api/exam/upload-question (Enhanced with Dynamic Scoring)", - "/api/exam/submit-solution (Dynamic Scoring)", - "/api/exam/leaderboard/ (Enhanced)", - "/api/exam/update-duration", - "/api/exam/info/", - "/api/exam/get-problem/", - "/api/exam/participants/", - "/api/exam/start-exam", - "/api/exam/stop-exam", - "/api/compiler/execute" - ] + "available_blueprints": blueprints_registered }), 404 @app.errorhandler(500) @@ -1139,9 +1323,8 @@ def internal_error(e): # Startup initialization def initialize_application(): - logger.info("🚀 Initializing OpenLearnX Backend with Dynamic Scoring") + logger.info("🚀 Initializing OpenLearnX Backend with FIXED Dynamic Scoring") - # Show blueprint registration status logger.info(f"📋 Blueprints registered: {len(blueprints_registered)}") for bp in blueprints_registered: logger.info(f" ✅ {bp}") @@ -1151,54 +1334,30 @@ def initialize_application(): for bp, error in blueprints_failed: logger.warning(f" ❌ {bp}: {error}") - # Test DB if MONGO_SERVICE_AVAILABLE: try: asyncio.get_event_loop().run_until_complete(mongo_service.init_db()) logger.info("✅ MongoDB initialized") - # Test direct connection db = get_db() db.command('ismaster') logger.info("✅ Direct MongoDB connection verified") except Exception as e: logger.error(f"❌ MongoDB init failed: {e}") - # Test Web3 if WEB3_SERVICE_AVAILABLE: if web3_service.w3.is_connected(): logger.info("✅ Web3 connected") else: logger.warning("⚠️ Web3 not connected") - # Test Docker - if COMPILER_SERVICE_AVAILABLE and not check_docker_availability(): - logger.warning("⚠️ Docker unavailable") - - # Log enhanced endpoints - logger.info("🔧 Enhanced exam endpoints with dynamic scoring:") - direct_endpoints = [ - "/api/exam/upload-question (Enhanced with Test Cases)", - "/api/exam/submit-solution (Dynamic Scoring System)", - "/api/exam/leaderboard/ (Enhanced Stats)", - "/api/exam/update-duration", - "/api/exam/info/", - "/api/exam/get-problem/", - "/api/exam/participants/", - "/api/exam/start-exam", - "/api/exam/stop-exam", - "/api/compiler/execute" - ] - for endpoint in direct_endpoints: - logger.info(f" ✅ {endpoint}") - - logger.info("🧮 Dynamic Scoring Features:") + logger.info("🧮 FIXED Dynamic Scoring Features:") scoring_features = [ - "Test case based scoring", - "Point distribution per test case", - "Host-defined correct solutions", - "Enhanced leaderboard with test details", - "Automatic output matching" + "Fixed test case execution", + "Proper participant name tracking", + "Enhanced debug endpoints", + "Fixed point distribution", + "Improved error handling" ] for feature in scoring_features: logger.info(f" 🎯 {feature}") @@ -1208,8 +1367,8 @@ def initialize_application(): if __name__ == '__main__': initialize_application() - logger.info("🚀 Starting OpenLearnX Backend Server with Dynamic Scoring") - logger.info("📚 Enhanced Features: Dynamic Test Case Scoring, Host Solutions, Point-based Tests") + logger.info("🚀 Starting OpenLearnX Backend Server with FIXED Dynamic Scoring") + logger.info("📚 Fixed Features: Proper Scoring Updates, Input Handling, Participant Tracking") logger.info("🌐 Server starting on http://0.0.0.0:5000") logger.info("🔧 All /api/* endpoints have CORS enabled")