some kinda

This commit is contained in:
5t4l1n
2025-07-26 22:20:50 +05:30
parent 818a268038
commit 8c56eb9e36
20 changed files with 5544 additions and 155 deletions
+474 -69
View File
@@ -7,142 +7,547 @@ from mongo_service import MongoService
from web3_service import Web3Service
import logging
# Import all route blueprints
from routes import auth, test_flow, certificate, dashboard, courses, quizzes, admin
# Load environment variables first
load_dotenv()
# Import all route blueprints
from routes import auth, test_flow, certificate, dashboard, courses, quizzes, admin, exam, compiler
# Import services after loading env vars
try:
from services.wallet_service import wallet_service
from services.real_compiler_service import real_compiler_service
WALLET_SERVICE_AVAILABLE = True
COMPILER_SERVICE_AVAILABLE = True
except ImportError as e:
logging.warning(f"Service import failed: {e}")
wallet_service = None
real_compiler_service = None
WALLET_SERVICE_AVAILABLE = False
COMPILER_SERVICE_AVAILABLE = False
# Initialize Flask app
app = Flask(__name__)
# Enhanced CORS configuration for admin panel with credentials support
# Enhanced CORS configuration for coding exam platform
CORS(app, resources={
r"/api/*": {
"origins": ["http://localhost:3000", "http://127.0.0.1:3000"],
"origins": [
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:3001",
"http://127.0.0.1:3001"
],
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True # ✅ Added for admin authentication
"allow_headers": [
"Content-Type",
"Authorization",
"X-Requested-With",
"Accept",
"Origin"
],
"supports_credentials": True,
"expose_headers": ["Authorization"]
}
})
# Configuration
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'your-secret-key')
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'openlearnx-secret-key-2024')
app.config['MONGODB_URI'] = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/')
app.config['WEB3_PROVIDER_URL'] = os.getenv('WEB3_PROVIDER_URL', 'http://127.0.0.1:8545')
app.config['CONTRACT_ADDRESS'] = os.getenv('CONTRACT_ADDRESS')
app.config['MINTER_PRIVATE_KEY'] = os.getenv('MINTER_PRIVATE_KEY')
app.config['CONTRACT_ADDRESS'] = os.getenv('CONTRACT_ADDRESS', '0x739f0aCef964f87Bc7974D972a811f8417d74B4C')
app.config['MINTER_PRIVATE_KEY'] = os.getenv('MINTER_PRIVATE_KEY', '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')
app.config['ADMIN_TOKEN'] = os.getenv('ADMIN_TOKEN', 'admin-secret-key')
# Initialize services
mongo_service = MongoService(app.config['MONGODB_URI'])
web3_service = Web3Service(app.config['WEB3_PROVIDER_URL'], app.config['CONTRACT_ADDRESS'])
# Blockchain configuration
app.config['IPFS_GATEWAY'] = os.getenv('IPFS_GATEWAY', 'https://ipfs.infura.io:5001')
app.config['IPFS_PROJECT_ID'] = os.getenv('IPFS_PROJECT_ID')
app.config['IPFS_PROJECT_SECRET'] = os.getenv('IPFS_PROJECT_SECRET')
# Configure logging BEFORE initializing services
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('openlearnx.log') if os.access('.', os.W_OK) else logging.NullHandler()
]
)
logger = logging.getLogger(__name__)
# Initialize services with error handling
try:
mongo_service = MongoService(app.config['MONGODB_URI'])
app.config['MONGO_SERVICE'] = mongo_service
MONGO_SERVICE_AVAILABLE = True
except Exception as e:
logging.error(f"Failed to initialize MongoDB service: {e}")
mongo_service = None
MONGO_SERVICE_AVAILABLE = False
try:
web3_service = Web3Service(
app.config['WEB3_PROVIDER_URL'],
app.config['CONTRACT_ADDRESS']
)
app.config['WEB3_SERVICE'] = web3_service
WEB3_SERVICE_AVAILABLE = True
except Exception as e:
logging.error(f"Failed to initialize Web3 service: {e}")
web3_service = None
WEB3_SERVICE_AVAILABLE = False
# Make services available to routes
app.config['MONGO_SERVICE'] = mongo_service
app.config['WEB3_SERVICE'] = web3_service
if WALLET_SERVICE_AVAILABLE:
app.config['WALLET_SERVICE'] = wallet_service
# Register all blueprints
app.register_blueprint(auth.bp, url_prefix='/api/auth')
app.register_blueprint(test_flow.bp, url_prefix='/api/test')
app.register_blueprint(certificate.bp, url_prefix='/api/certificate')
app.register_blueprint(dashboard.bp, url_prefix='/api/dashboard')
app.register_blueprint(courses.bp, url_prefix='/api/courses')
app.register_blueprint(quizzes.bp, url_prefix='/api/quizzes')
app.register_blueprint(admin.bp, url_prefix="/api/admin")
if COMPILER_SERVICE_AVAILABLE:
app.config['REAL_COMPILER_SERVICE'] = real_compiler_service
# ✅ DEFINE check_docker_availability BEFORE using it
def check_docker_availability():
"""Check if Docker is available for compiler service"""
try:
import docker
client = docker.from_env()
client.ping()
return True
except Exception:
return False
# Register all blueprints with error handling
blueprints = [
(auth.bp, '/api/auth'),
(test_flow.bp, '/api/test'),
(certificate.bp, '/api/certificate'),
(dashboard.bp, '/api/dashboard'),
(courses.bp, '/api/courses'),
(quizzes.bp, '/api/quizzes'),
(admin.bp, '/api/admin'),
(exam.bp, '/api/exam'), # Coding exam routes
(compiler.bp, '/api/compiler'), # Compiler routes
]
for blueprint, url_prefix in blueprints:
try:
app.register_blueprint(blueprint, url_prefix=url_prefix)
logging.info(f"✅ Registered blueprint: {url_prefix}")
print(f"✅ Registered blueprint: {url_prefix}")
except Exception as e:
logging.error(f"❌ Failed to register blueprint {url_prefix}: {e}")
print(f"❌ Failed to register blueprint {url_prefix}: {e}")
# Debug routes
@app.route('/debug/routes')
def debug_routes():
"""Debug route to see all registered routes"""
routes = []
for rule in app.url_map.iter_rules():
routes.append({
'endpoint': rule.endpoint,
'methods': list(rule.methods),
'rule': str(rule)
})
return jsonify({
"total_routes": len(routes),
"routes": sorted(routes, key=lambda x: x['rule'])
})
@app.route('/debug/exam-routes')
def debug_exam_routes():
"""Debug exam-specific routes"""
exam_routes = []
for rule in app.url_map.iter_rules():
if '/exam' in str(rule):
exam_routes.append({
'endpoint': rule.endpoint,
'methods': list(rule.methods),
'rule': str(rule)
})
return jsonify({
"exam_routes": exam_routes,
"exam_blueprint_registered": hasattr(exam, 'bp')
})
@app.route('/debug/services')
def debug_services():
"""Debug service availability"""
return jsonify({
"services": {
"mongodb": MONGO_SERVICE_AVAILABLE,
"web3": WEB3_SERVICE_AVAILABLE,
"wallet": WALLET_SERVICE_AVAILABLE,
"compiler": COMPILER_SERVICE_AVAILABLE,
"docker": check_docker_availability()
},
"app_config_keys": list(app.config.keys()),
"blueprint_count": len(app.blueprints)
})
# Direct exam test route
@app.route('/api/exam/test-direct', methods=['GET', 'POST', 'OPTIONS'])
def test_exam_direct():
"""Direct test route for exam functionality"""
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,POST,OPTIONS")
return response
return jsonify({
"success": True,
"message": "Direct exam route is working",
"method": request.method,
"timestamp": os.popen('date').read().strip(),
"data": request.json if request.method == "POST" else None
})
# Health check endpoints
@app.route('/')
def health_check():
return jsonify({
"status": "OpenLearnX API is running",
"version": "1.0.0",
"version": "2.0.0",
"features": {
"blockchain": WEB3_SERVICE_AVAILABLE,
"coding_exams": COMPILER_SERVICE_AVAILABLE,
"wallet_auth": WALLET_SERVICE_AVAILABLE,
"database": MONGO_SERVICE_AVAILABLE,
"real_compiler": COMPILER_SERVICE_AVAILABLE
},
"endpoints": {
"auth": "/api/auth",
"courses": "/api/courses",
"admin": "/api/admin",
"dashboard": "/api/dashboard",
"certificates": "/api/certificate",
"quizzes": "/api/quizzes"
}
"quizzes": "/api/quizzes",
"coding_exams": "/api/exam",
"compiler": "/api/compiler"
},
"debug_endpoints": [
"/debug/routes",
"/debug/exam-routes",
"/debug/services",
"/api/exam/test-direct"
]
})
@app.route('/api/health')
def api_health():
"""Comprehensive API health check"""
health_status = {
"status": "healthy",
"timestamp": os.popen('date').read().strip(),
"services": {
"mongodb": MONGO_SERVICE_AVAILABLE,
"web3": WEB3_SERVICE_AVAILABLE,
"wallet": WALLET_SERVICE_AVAILABLE,
"compiler": COMPILER_SERVICE_AVAILABLE,
"docker": check_docker_availability()
},
"configuration": {
"cors_enabled": True,
"debug_mode": app.debug,
"secret_key_set": bool(app.config.get('SECRET_KEY')),
"admin_token_set": bool(app.config.get('ADMIN_TOKEN'))
},
"blueprints_registered": list(app.blueprints.keys())
}
# Check MongoDB connection
if MONGO_SERVICE_AVAILABLE:
try:
from pymongo import MongoClient
client = MongoClient(app.config['MONGODB_URI'])
client.admin.command('ismaster')
health_status["services"]["mongodb_connection"] = "connected"
except Exception as e:
health_status["services"]["mongodb_connection"] = f"error: {str(e)}"
health_status["status"] = "degraded"
# Check Web3 connection
if WEB3_SERVICE_AVAILABLE:
try:
if web3_service and web3_service.w3.is_connected():
health_status["services"]["web3_connection"] = "connected"
else:
health_status["services"]["web3_connection"] = "disconnected"
health_status["status"] = "degraded"
except Exception as e:
health_status["services"]["web3_connection"] = f"error: {str(e)}"
health_status["status"] = "degraded"
status_code = 200 if health_status["status"] == "healthy" else 503
return jsonify(health_status), status_code
@app.route('/api/admin/health')
def admin_health():
"""Admin-specific health check"""
return jsonify({
"status": "Admin API is running",
"admin_token_configured": bool(app.config.get('ADMIN_TOKEN')),
"admin_endpoints": [
"/api/admin/dashboard",
"/api/admin/courses",
"/api/admin/courses/<id>",
"/api/admin/test" # ✅ Added test endpoint
"/api/admin/test",
"/api/admin/initialize"
],
"exam_endpoints": [
"/api/exam/create-exam",
"/api/exam/join-exam",
"/api/exam/join-exam-wallet",
"/api/exam/start-exam",
"/api/exam/submit-solution",
"/api/exam/leaderboard/<exam_code>",
"/api/exam/host-dashboard/<exam_code>"
],
"compiler_endpoints": [
"/api/compiler/languages",
"/api/compiler/execute",
"/api/compiler/execute-async",
"/api/compiler/status/<execution_id>",
"/api/compiler/test",
"/api/compiler/stats"
]
})
# Error handlers
@app.errorhandler(404)
def not_found(error):
return jsonify({"error": "Endpoint not found"}), 404
return jsonify({
"error": "Endpoint not found",
"message": "The requested API endpoint does not exist",
"available_endpoints": [
"/api/auth", "/api/courses", "/api/admin",
"/api/exam", "/api/dashboard", "/api/certificate",
"/api/compiler"
],
"debug_endpoints": [
"/debug/routes",
"/debug/exam-routes",
"/debug/services"
]
}), 404
@app.errorhandler(500)
def internal_error(error):
app.logger.error(f"Internal server error: {str(error)}")
return jsonify({"error": "Internal server error"}), 500
return jsonify({
"error": "Internal server error",
"message": "An unexpected error occurred on the server"
}), 500
@app.errorhandler(403)
def forbidden(error):
return jsonify({
"error": "Forbidden",
"message": "Access denied - check your authentication"
}), 403
@app.errorhandler(401)
def unauthorized(error):
return jsonify({
"error": "Unauthorized",
"message": "Authentication required"
}), 401
@app.errorhandler(Exception)
def handle_error(error):
app.logger.error(f"Unhandled error: {str(error)}")
return jsonify({"error": "An unexpected error occurred"}), 500
# Enable logging for admin operations
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' # ✅ Enhanced logging format
)
logger = logging.getLogger(__name__)
return jsonify({
"error": "An unexpected error occurred",
"type": type(error).__name__
}), 500
# Request logging and CORS handling
@app.before_request
def log_request_info():
"""Enhanced request logging"""
if '/api/admin' in request.path:
# ✅ Enhanced admin request logging
auth_header = request.headers.get('Authorization', 'No auth header')
logger.info(f"Admin request: {request.method} {request.path} | Auth: {auth_header}")
logger.info(f"Admin request: {request.method} {request.path} | Auth: {auth_header[:20]}...")
if '/api/exam' in request.path:
logger.info(f"Exam request: {request.method} {request.path}")
print(f"📝 Exam request: {request.method} {request.path}")
if '/api/compiler' in request.path:
logger.info(f"Compiler request: {request.method} {request.path}")
# ✅ Add OPTIONS handler for CORS preflight
@app.before_request
def handle_preflight():
"""Handle CORS preflight requests"""
if request.method == "OPTIONS":
response = jsonify()
response = jsonify({'status': 'ok'})
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add('Access-Control-Allow-Headers', "*")
response.headers.add('Access-Control-Allow-Methods', "*")
response.headers.add('Access-Control-Allow-Headers', "Content-Type,Authorization")
response.headers.add('Access-Control-Allow-Methods', "GET,POST,PUT,DELETE,OPTIONS")
response.headers.add('Access-Control-Allow-Credentials', 'true')
return response
if __name__ == '__main__':
@app.after_request
def after_request(response):
"""Add security headers"""
response.headers.add('X-Content-Type-Options', 'nosniff')
response.headers.add('X-Frame-Options', 'DENY')
response.headers.add('X-XSS-Protection', '1; mode=block')
return response
# Startup function
def initialize_application():
"""Initialize application with comprehensive error handling"""
try:
# ✅ Enhanced database initialization with better error handling
loop = asyncio.get_event_loop()
loop.run_until_complete(mongo_service.init_db())
logger.info("✅ Database initialized successfully")
logger.info("🚀 Initializing OpenLearnX Backend...")
print("🚀 Initializing OpenLearnX Backend...")
# Test MongoDB connection
from pymongo import MongoClient
client = MongoClient(app.config['MONGODB_URI'])
client.admin.command('ismaster')
logger.info("✅ MongoDB connection verified")
# Test MongoDB connection
if MONGO_SERVICE_AVAILABLE:
try:
loop = asyncio.get_event_loop()
loop.run_until_complete(mongo_service.init_db())
logger.info("✅ Database initialized successfully")
print("✅ Database initialized successfully")
from pymongo import MongoClient
client = MongoClient(app.config['MONGODB_URI'])
client.admin.command('ismaster')
logger.info("✅ MongoDB connection verified")
print("✅ MongoDB connection verified")
except Exception as e:
logger.error(f"❌ MongoDB initialization failed: {e}")
print(f"❌ MongoDB initialization failed: {e}")
logger.info("✅ OpenLearnX backend starting...")
logger.info(f"✅ Admin panel available at: http://localhost:3000/admin/login")
logger.info(f"✅ API health check: http://127.0.0.1:5000")
logger.info(f"✅ Admin health check: http://127.0.0.1:5000/api/admin/health")
# Test Web3 connection
if WEB3_SERVICE_AVAILABLE:
try:
if web3_service.w3.is_connected():
logger.info("✅ Web3 connection verified")
print("✅ Web3 connection verified")
else:
logger.warning("⚠️ Web3 connection failed - blockchain features disabled")
print("⚠️ Web3 connection failed - blockchain features disabled")
except Exception as e:
logger.warning(f"⚠️ Web3 connection error: {e}")
print(f"⚠️ Web3 connection error: {e}")
# ✅ Log admin token for debugging
admin_token = os.getenv('ADMIN_TOKEN', 'admin-secret-key')
logger.info(f"✅ Admin token configured: {admin_token[:8]}...")
# Test Docker connection for compiler
if COMPILER_SERVICE_AVAILABLE:
try:
docker_available = check_docker_availability()
if docker_available:
logger.info("✅ Docker connection verified - Real compiler available")
print("✅ Docker connection verified - Real compiler available")
else:
logger.warning("⚠️ Docker not available - Compiler features limited")
print("⚠️ Docker not available - Compiler features limited")
except Exception as e:
logger.warning(f"⚠️ Docker connection error: {e}")
print(f"⚠️ Docker connection error: {e}")
# Log configuration
logger.info("📋 Configuration Summary:")
print("📋 Configuration Summary:")
config_items = [
("MongoDB", MONGO_SERVICE_AVAILABLE),
("Blockchain", WEB3_SERVICE_AVAILABLE),
("Wallet Service", WALLET_SERVICE_AVAILABLE),
("Compiler Service", COMPILER_SERVICE_AVAILABLE),
("Docker", check_docker_availability())
]
for name, available in config_items:
status = "✅ Connected" if available else "❌ Unavailable"
logger.info(f"{name}: {status}")
print(f"{name}: {status}")
# Log access URLs
logger.info("🌐 Access URLs:")
print("🌐 Access URLs:")
urls = [
("API Health", "http://127.0.0.1:5000/api/health"),
("Admin Panel", "http://localhost:3000/admin/login"),
("Coding Exams", "http://localhost:3000/coding"),
("Real Compiler", "http://localhost:3000/compiler"),
("Join Exam", "http://localhost:3000/coding/join"),
("Wallet Join", "http://localhost:3000/coding/join-wallet")
]
for name, url in urls:
logger.info(f"{name}: {url}")
print(f"{name}: {url}")
# Debug URLs
print("🔧 Debug URLs:")
debug_urls = [
("All Routes", "http://127.0.0.1:5000/debug/routes"),
("Exam Routes", "http://127.0.0.1:5000/debug/exam-routes"),
("Services", "http://127.0.0.1:5000/debug/services"),
("Direct Test", "http://127.0.0.1:5000/api/exam/test-direct")
]
for name, url in debug_urls:
print(f"{name}: {url}")
# Log compiler features
if COMPILER_SERVICE_AVAILABLE:
logger.info("💻 Compiler Features:")
print("💻 Compiler Features:")
features = [
"Multi-language support: Python, Java, C++, C, JavaScript, Go, Rust, Bash",
"Real-time code execution with output capture",
"Secure Docker containerization",
"Resource monitoring and limits"
]
for feature in features:
logger.info(f"{feature}")
print(f"{feature}")
# Log admin token (partially masked)
admin_token = app.config.get('ADMIN_TOKEN', 'admin-secret-key')
if admin_token:
logger.info(f"🔑 Admin token configured: {admin_token[:8]}...")
print(f"🔑 Admin token configured: {admin_token[:8]}...")
return True
except Exception as e:
logger.error(f"❌ Failed to initialize: {str(e)}")
logger.error("Make sure MongoDB is running and accessible")
logger.error(f"❌ Failed to initialize application: {str(e)}")
print(f"❌ Failed to initialize application: {str(e)}")
logger.error("Make sure MongoDB and Docker are running and accessible")
print("Make sure MongoDB and Docker are running and accessible")
return False
if __name__ == '__main__':
# Initialize application
init_success = initialize_application()
# ✅ Enhanced Flask app configuration
app.run(
debug=True,
host='0.0.0.0',
port=5000,
threaded=True # Better for handling multiple requests
)
if not init_success:
logger.error("❌ Application initialization failed - some features may not work")
print("❌ Application initialization failed - some features may not work")
try:
logger.info("🚀 Starting OpenLearnX Backend Server...")
print("🚀 Starting OpenLearnX Backend Server...")
logger.info("📚 Features: Blockchain Certificates, Coding Exams, Wallet Auth, Real Multi-language Compiler")
print("📚 Features: Blockchain Certificates, Coding Exams, Wallet Auth, Real Multi-language Compiler")
# Start Flask application
app.run(
debug=True,
host='0.0.0.0',
port=5000,
threaded=True,
use_reloader=False
)
except KeyboardInterrupt:
logger.info("👋 Server stopped by user")
print("👋 Server stopped by user")
except Exception as e:
logger.error(f"❌ Server startup failed: {str(e)}")
print(f"❌ Server startup failed: {str(e)}")
+227 -75
View File
@@ -1,88 +1,240 @@
from flask import Blueprint, jsonify, request, current_app
import requests
from bson import ObjectId
from flask import Blueprint, request, jsonify, session
from functools import wraps
import subprocess
import tempfile
import os
import time
import uuid
from datetime import datetime
import docker
import psutil
bp = Blueprint('coding', __name__)
PISTON_API_URL = "https://emkc.org/api/v2/piston/execute"
@bp.route("/problems", methods=["GET"])
async def get_problems():
mongo = current_app.config['MONGO_SERVICE']
problems = await mongo.db.coding_problems.find().to_list(100)
for p in problems:
p['_id'] = str(p['_id'])
return jsonify(problems)
@bp.route("/problems/<problem_id>", methods=["GET"])
async def get_problem(problem_id):
mongo = current_app.config['MONGO_SERVICE']
prob = await mongo.db.coding_problems.find_one({"_id": ObjectId(problem_id)})
if not prob:
return jsonify({"error": "Problem not found"}), 404
prob['_id'] = str(prob['_id'])
return jsonify(prob)
@bp.route("/run", methods=["POST"])
async def run_code():
data = request.json
problem_id = data.get("problem_id")
code = data.get("code")
language = data.get("language")
mongo = current_app.config['MONGO_SERVICE']
problem = await mongo.db.coding_problems.find_one({"_id": ObjectId(problem_id)})
if not problem:
return jsonify({"error": "Problem not found"}), 404
# Concatenate all test case inputs
input_data = '\n'.join([tc['input'] for tc in problem['test_cases']])
def secure_execution_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Check if user is in secure coding mode
if not session.get('secure_coding_mode'):
return jsonify({"error": "Secure coding mode required"}), 403
return f(*args, **kwargs)
return decorated_function
@bp.route("/start-session", methods=["POST"])
def start_coding_session():
"""Start a secure coding session"""
try:
resp = requests.post(
PISTON_API_URL,
json={
"language": language,
"source": code,
"input": input_data
},
timeout=10,
)
resp.raise_for_status()
result = resp.json()
data = request.json
course_id = data.get('course_id')
lesson_id = data.get('lesson_id')
session_id = str(uuid.uuid4())
session['coding_session_id'] = session_id
session['secure_coding_mode'] = True
session['start_time'] = datetime.now().isoformat()
session['course_id'] = course_id
session['lesson_id'] = lesson_id
return jsonify({
"success": True,
"session_id": session_id,
"message": "Secure coding session started",
"restrictions": {
"copy_paste_disabled": True,
"browser_locked": True,
"extensions_blocked": True,
"virtual_detection": True
}
})
except Exception as e:
return jsonify({"error": str(e)}), 500
# Compare output against expected (simple line-by-line check)
output_lines = result.get("output", "").strip().split('\n')
expected_outputs = [tc['expected_output'].strip() for tc in problem['test_cases']]
correct = output_lines == expected_outputs
@bp.route("/execute", methods=["POST"])
@secure_execution_required
def execute_code():
"""Execute code securely in isolated environment"""
try:
data = request.json
code = data.get('code')
language = data.get('language', 'python')
test_cases = data.get('test_cases', [])
if not code:
return jsonify({"error": "No code provided"}), 400
# Log coding attempt
log_coding_attempt(session['coding_session_id'], code, language)
# Execute code in secure container
result = execute_in_container(code, language, test_cases)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
return jsonify({
"output": result.get("output"),
"error": result.get("stderr"),
"runtime": result.get("stats", {}).get("duration"),
"correct": correct,
@bp.route("/submit-test", methods=["POST"])
@secure_execution_required
def submit_coding_test():
"""Submit coding test for evaluation"""
try:
data = request.json
code = data.get('code')
problem_id = data.get('problem_id')
# Validate against test cases
test_result = validate_test_submission(code, problem_id)
# Store submission
submission_id = store_submission(
session['coding_session_id'],
session['course_id'],
problem_id,
code,
test_result
)
return jsonify({
"success": True,
"submission_id": submission_id,
"score": test_result['score'],
"passed_tests": test_result['passed'],
"total_tests": test_result['total'],
"feedback": test_result['feedback']
})
except Exception as e:
return jsonify({"error": str(e)}), 500
def execute_in_container(code, language, test_cases):
"""Execute code in secure Docker container"""
try:
client = docker.from_env()
# Language-specific container configuration
containers = {
'python': 'python:3.9-alpine',
'java': 'openjdk:11-alpine',
'javascript': 'node:16-alpine'
}
if language not in containers:
return {"error": "Unsupported language"}
# Create temporary file
with tempfile.NamedTemporaryFile(mode='w', suffix=f'.{get_file_extension(language)}', delete=False) as f:
f.write(code)
temp_file = f.name
try:
# Run container with security restrictions
container = client.containers.run(
containers[language],
command=get_run_command(language, temp_file),
volumes={os.path.dirname(temp_file): {'bind': '/app', 'mode': 'ro'}},
working_dir='/app',
mem_limit='128m',
cpu_period=100000,
cpu_quota=50000, # 50% CPU limit
network_mode='none', # No network access
remove=True,
timeout=10, # 10 second timeout
detach=False
)
output = container.decode('utf-8')
# Run test cases if provided
test_results = []
if test_cases:
for test in test_cases:
test_result = run_test_case(code, language, test)
test_results.append(test_result)
return {
"success": True,
"output": output,
"test_results": test_results,
"execution_time": "< 10s"
}
finally:
os.unlink(temp_file)
except docker.errors.ContainerError as e:
return {"error": f"Runtime error: {e}"}
except docker.errors.ImageNotFound:
return {"error": "Language runtime not available"}
except Exception as e:
return {"error": f"Execution failed: {str(e)}"}
def get_file_extension(language):
extensions = {
'python': 'py',
'java': 'java',
'javascript': 'js'
}
return extensions.get(language, 'txt')
def get_run_command(language, filename):
commands = {
'python': f'python /app/{os.path.basename(filename)}',
'java': f'javac /app/{os.path.basename(filename)} && java -cp /app {os.path.splitext(os.path.basename(filename))[0]}',
'javascript': f'node /app/{os.path.basename(filename)}'
}
return commands.get(language)
def log_coding_attempt(session_id, code, language):
"""Log all coding attempts for monitoring"""
from pymongo import MongoClient
client = MongoClient(os.getenv('MONGODB_URI', 'mongodb://localhost:27017/'))
db = client.openlearnx
db.coding_logs.insert_one({
"session_id": session_id,
"code": code,
"language": language,
"timestamp": datetime.now(),
"ip_address": request.remote_addr,
"user_agent": request.headers.get('User-Agent')
})
@bp.route("/submit", methods=["POST"])
async def submit_solution():
# Same as run_code, but can mark problem as solved
user = await get_authenticated_user()
if not user:
return jsonify({"error": "Unauthorized"}), 401
def validate_test_submission(code, problem_id):
"""Validate code against predefined test cases"""
# Load test cases for the problem
test_cases = get_problem_test_cases(problem_id)
passed = 0
total = len(test_cases)
feedback = []
for i, test_case in enumerate(test_cases):
result = run_test_case(code, 'python', test_case)
if result['passed']:
passed += 1
feedback.append(f"Test {i+1}: ✅ Passed")
else:
feedback.append(f"Test {i+1}: ❌ Failed - {result['error']}")
score = (passed / total) * 100
return {
"score": score,
"passed": passed,
"total": total,
"feedback": feedback
}
# Run the code first
result = await run_code()
jres = result.get_json()
if jres.get("correct"):
mongo = current_app.config['MONGO_SERVICE']
# Record that user solved problem
await mongo.db.user_solutions.update_one(
{"user_id": user['_id'], "problem_id": jres.get('problem_id')},
{"$set": {"solved": True, "solved_at": datetime.utcnow()}},
upsert=True
)
return jsonify(jres)
``
def get_problem_test_cases(problem_id):
"""Get test cases for a specific problem"""
# This would load from your database
test_cases_db = {
"python-basics-1": [
{"input": "hello", "expected_output": "HELLO"},
{"input": "world", "expected_output": "WORLD"}
],
"java-oop-1": [
{"input": "5", "expected_output": "25"},
{"input": "10", "expected_output": "100"}
]
}
return test_cases_db.get(problem_id, [])
+165
View File
@@ -0,0 +1,165 @@
from flask import Blueprint, request, jsonify, session
from services.real_compiler_service import real_compiler_service
import uuid
from datetime import datetime
bp = Blueprint('compiler', __name__)
@bp.route("/languages", methods=["GET"])
def get_supported_languages():
"""Get list of supported programming languages"""
try:
languages = real_compiler_service.get_supported_languages()
return jsonify({
"success": True,
"languages": languages,
"total_languages": len(languages)
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/execute", methods=["POST"])
def execute_code():
"""Execute code and return real output"""
try:
data = request.json
# Validate input
code = data.get('code', '').strip()
language = data.get('language', 'python')
input_data = data.get('input', '')
if not code:
return jsonify({"error": "No code provided"}), 400
if language not in [lang['id'] for lang in real_compiler_service.get_supported_languages()]:
return jsonify({"error": f"Language '{language}' not supported"}), 400
# Generate execution ID
execution_id = str(uuid.uuid4())
# Execute code
result = real_compiler_service.execute_code(
code=code,
language=language,
input_data=input_data,
execution_id=execution_id
)
return jsonify(result)
except Exception as e:
return jsonify({"error": f"Execution failed: {str(e)}"}), 500
@bp.route("/execute-async", methods=["POST"])
def execute_code_async():
"""Start asynchronous code execution"""
try:
data = request.json
execution_id = str(uuid.uuid4())
# Add to execution queue
real_compiler_service.execution_queue.put({
'execution_id': execution_id,
'code': data.get('code'),
'language': data.get('language', 'python'),
'input_data': data.get('input', ''),
'callback_url': data.get('callback_url')
})
return jsonify({
"success": True,
"execution_id": execution_id,
"message": "Code execution started",
"status_url": f"/api/compiler/status/{execution_id}"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/status/<execution_id>", methods=["GET"])
def get_execution_status(execution_id):
"""Get status of code execution"""
try:
status = real_compiler_service.get_execution_status(execution_id)
if status:
return jsonify({
"success": True,
"execution_id": execution_id,
"status": status['status'],
"start_time": status['start_time'].isoformat(),
"language": status['language']
})
else:
return jsonify({
"success": False,
"error": "Execution not found"
}), 404
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/cancel/<execution_id>", methods=["POST"])
def cancel_execution(execution_id):
"""Cancel a running execution"""
try:
success = real_compiler_service.cancel_execution(execution_id)
return jsonify({
"success": success,
"message": "Execution cancelled" if success else "Execution not found"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/test", methods=["POST"])
def test_compiler():
"""Test compiler with sample code"""
try:
language = request.json.get('language', 'python')
test_codes = {
'python': 'print("Hello from OpenLearnX Python Compiler!")\nprint("Current time:", __import__("datetime").datetime.now())',
'java': 'public class Main {\n public static void main(String[] args) {\n System.out.println("Hello from OpenLearnX Java Compiler!");\n }\n}',
'cpp': '#include <iostream>\nint main() {\n std::cout << "Hello from OpenLearnX C++ Compiler!" << std::endl;\n return 0;\n}',
'javascript': 'console.log("Hello from OpenLearnX JavaScript Compiler!");',
'go': 'package main\nimport "fmt"\nfunc main() {\n fmt.Println("Hello from OpenLearnX Go Compiler!")\n}',
'rust': 'fn main() {\n println!("Hello from OpenLearnX Rust Compiler!");\n}'
}
test_code = test_codes.get(language, test_codes['python'])
result = real_compiler_service.execute_code(
code=test_code,
language=language,
input_data=""
)
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/stats", methods=["GET"])
def get_compiler_stats():
"""Get compiler service statistics"""
try:
active_executions = len(real_compiler_service.active_executions)
queue_size = real_compiler_service.execution_queue.qsize()
supported_languages = len(real_compiler_service.language_configs)
return jsonify({
"success": True,
"stats": {
"active_executions": active_executions,
"queue_size": queue_size,
"supported_languages": supported_languages,
"max_concurrent": real_compiler_service.max_concurrent_executions
},
"uptime": datetime.now().isoformat()
})
except Exception as e:
return jsonify({"error": str(e)}), 500
+515
View File
@@ -0,0 +1,515 @@
from flask import Blueprint, request, jsonify, session
import uuid
import random
import string
from datetime import datetime, timedelta
from pymongo import MongoClient
import os
bp = Blueprint('exam', __name__)
# MongoDB connection
mongo_uri = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/')
client = MongoClient(mongo_uri)
db = client.openlearnx
def generate_exam_code():
"""Generate a unique 6-character exam code"""
while True:
code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
if not db.exams.find_one({"exam_code": code}):
return code
@bp.route("/create-exam", methods=["POST", "OPTIONS"])
def create_exam():
"""Create a new coding exam"""
# Handle OPTIONS request for CORS
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:
print(f"Received create-exam request")
data = request.json
print(f"Request data: {data}")
if not data:
print("❌ No data provided")
return jsonify({"error": "No data provided"}), 400
# Check for basic required fields
if not data.get('title'):
print("❌ Missing title")
return jsonify({"error": "Missing required field: title"}), 400
if not data.get('host_name'):
print("❌ Missing host_name")
return jsonify({"error": "Missing required field: host_name"}), 400
# Handle different problem data formats
problem_title = data.get('problem_title') or data.get('title') or 'Coding Challenge'
problem_description = data.get('problem_description') or f"Solve the {problem_title} problem"
# Handle problem_id if provided
if data.get('problem_id'):
problem_title = problem_title or data.get('problem_id').replace('-', ' ').title()
print(f"Using problem_id: {data.get('problem_id')}")
exam_code = generate_exam_code()
exam = {
"exam_code": exam_code,
"title": data.get('title'),
"host_name": data.get('host_name'),
"created_at": datetime.now(),
"status": "waiting",
"duration_minutes": data.get('duration_minutes', 30),
"max_participants": data.get('max_participants', 50),
"problem": {
"title": problem_title,
"description": problem_description,
"function_name": data.get('function_name', 'solve'),
"languages": data.get('languages', ['python']),
"test_cases": data.get('test_cases', [
{
"input": "hello world",
"expected_output": "Hello World",
"description": "Basic capitalization test"
}
]),
"starter_code": data.get('starter_code', {
'python': 'def solve(input_string):\n # Write your solution here\n return input_string.title()',
'java': 'public String solve(String inputString) {\n // Write your solution here\n return inputString;\n}',
'javascript': 'function solve(inputString) {\n // Write your solution here\n return inputString;\n}'
}),
"constraints": data.get('constraints', ['Input will be a string', 'Length between 1-1000 characters']),
"examples": data.get('examples', [
{
"input": "hello world",
"expected_output": "Hello World",
"description": "Capitalize each word"
}
])
},
"participants": [],
"leaderboard": [],
"start_time": None,
"end_time": None
}
print(f"✅ Creating exam with code: {exam_code}")
print(f"✅ Problem title: {problem_title}")
# Insert into database
result = db.exams.insert_one(exam)
print(f"✅ Exam created successfully with ID: {result.inserted_id}")
return jsonify({
"success": True,
"exam_code": exam_code,
"exam_id": str(result.inserted_id),
"message": f"Exam created successfully! Share code: {exam_code}",
"exam_details": {
"title": exam['title'],
"problem_title": problem_title,
"duration": exam['duration_minutes'],
"max_participants": exam['max_participants'],
"languages": exam['problem']['languages']
}
})
except Exception as e:
print(f"❌ Error creating exam: {str(e)}")
import traceback
traceback.print_exc()
return jsonify({"error": f"Failed to create exam: {str(e)}"}), 500
@bp.route("/join-exam", methods=["POST", "OPTIONS"])
def join_exam():
"""Student joins exam using unique code and their name"""
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:
# Debug logging for the request
print(f"🔍 Raw request data: {request.data}")
print(f"🔍 Content-Type: {request.headers.get('Content-Type')}")
data = request.json
print(f"🔍 Parsed JSON data: {data}")
if not data:
print("❌ No JSON data received")
return jsonify({"error": "No data provided"}), 400
exam_code = data.get('exam_code', '').upper().strip()
student_name = data.get('student_name', '').strip()
print(f"📝 Join exam request - Code: {exam_code}, Name: {student_name}")
# Enhanced validation with detailed error messages
if not exam_code:
print("❌ Missing exam_code")
return jsonify({"error": "Exam code is required"}), 400
if not student_name:
print("❌ Missing student_name")
return jsonify({"error": "Student name is required"}), 400
# Check if exam exists
exam = db.exams.find_one({"exam_code": exam_code})
if not exam:
print(f"❌ Exam not found: {exam_code}")
return jsonify({"error": "Invalid exam code"}), 404
print(f"✅ Found exam: {exam['title']} (Status: {exam['status']})")
# Check exam status
if exam['status'] == 'completed':
print("❌ Exam already completed")
return jsonify({"error": "This exam has already ended"}), 400
# Check capacity
current_participants = exam.get('participants', [])
max_participants = exam.get('max_participants', 50)
if len(current_participants) >= max_participants:
print(f"❌ Exam full: {len(current_participants)}/{max_participants}")
return jsonify({"error": "Exam is full"}), 400
# Check if name is already taken
existing_names = [p['name'].lower() for p in current_participants]
if student_name.lower() in existing_names:
print(f"❌ Name already taken: {student_name}")
return jsonify({"error": "Name already taken. Please choose a different name."}), 400
# Create new participant
participant = {
"name": student_name,
"joined_at": datetime.now(),
"session_id": str(uuid.uuid4()),
"score": 0,
"submission": None,
"language": None,
"submission_time": None,
"completed": False,
"rank": 0,
"test_results": []
}
# Add participant to exam
result = db.exams.update_one(
{"exam_code": exam_code},
{"$push": {"participants": participant}}
)
if result.modified_count == 0:
print("❌ Failed to add participant to database")
return jsonify({"error": "Failed to join exam"}), 500
# Set session data
session['exam_code'] = exam_code
session['student_name'] = student_name
session['session_id'] = participant['session_id']
print(f"✅ Participant {student_name} joined exam {exam_code}")
return jsonify({
"success": True,
"message": f"Successfully joined exam: {exam['title']}",
"exam_info": {
"title": exam['title'],
"duration_minutes": exam['duration_minutes'],
"status": exam['status'],
"participants_count": len(current_participants) + 1,
"max_participants": max_participants,
"languages": exam.get('problem', {}).get('languages', ['python']),
"problem_title": exam.get('problem', {}).get('title', '')
}
})
except Exception as e:
print(f"❌ Error joining exam: {str(e)}")
import traceback
traceback.print_exc()
return jsonify({"error": f"Failed to join exam: {str(e)}"}), 500
@bp.route("/start-exam", methods=["POST", "OPTIONS"])
def start_exam():
"""Host starts the 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.json
exam_code = data.get('exam_code')
print(f"📝 Start exam request - Code: {exam_code}")
exam = db.exams.find_one({"exam_code": exam_code})
if not exam:
return jsonify({"error": "Exam not found"}), 404
if exam['status'] != 'waiting':
return jsonify({"error": "Exam has already started or ended"}), 400
start_time = datetime.now()
end_time = start_time + timedelta(minutes=exam['duration_minutes'])
db.exams.update_one(
{"exam_code": exam_code},
{
"$set": {
"status": "active",
"start_time": start_time,
"end_time": end_time
}
}
)
print(f"✅ Exam {exam_code} started successfully")
return jsonify({
"success": True,
"message": "Exam started successfully!",
"start_time": start_time.isoformat(),
"end_time": end_time.isoformat(),
"participants_count": len(exam.get('participants', []))
})
except Exception as e:
print(f"❌ Error starting exam: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/leaderboard/<exam_code>", methods=["GET", "OPTIONS"])
def get_leaderboard(exam_code):
"""Get real-time leaderboard visible to all participants"""
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"📝 Leaderboard request - Code: {exam_code}")
exam = db.exams.find_one({"exam_code": exam_code.upper()})
if not exam:
return jsonify({"error": "Exam not found"}), 404
participants = exam.get('participants', [])
# Sort by score and submission time
completed_participants = [p for p in participants if p.get('completed', False)]
leaderboard = sorted(
completed_participants,
key=lambda x: (-x.get('score', 0), x.get('submission_time', datetime.now()))
)
# Add rank to each participant
for i, participant in enumerate(leaderboard):
participant['rank'] = i + 1
waiting_participants = [p for p in participants if not p.get('completed', False)]
# Calculate statistics
total_score = sum(p.get('score', 0) for p in completed_participants)
avg_score = total_score / len(completed_participants) if completed_participants else 0
return jsonify({
"success": True,
"exam_info": {
"title": exam['title'],
"status": exam['status'],
"duration_minutes": exam['duration_minutes'],
"start_time": exam.get('start_time'),
"end_time": exam.get('end_time'),
"problem_title": exam.get('problem', {}).get('title', '')
},
"leaderboard": leaderboard,
"waiting_participants": waiting_participants,
"stats": {
"total_participants": len(participants),
"completed_submissions": len(completed_participants),
"waiting_submissions": len(waiting_participants),
"average_score": round(avg_score, 1),
"highest_score": max((p.get('score', 0) for p in completed_participants), default=0)
}
})
except Exception as e:
print(f"❌ Error getting leaderboard: {str(e)}")
return jsonify({"error": str(e)}), 500
@bp.route("/get-problem/<exam_code>", methods=["GET", "OPTIONS"])
def get_exam_problem(exam_code):
"""Get problem details for participants"""
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:
exam = db.exams.find_one({"exam_code": exam_code.upper()})
if not exam:
return jsonify({"error": "Exam not found"}), 404
return jsonify({
"success": True,
"problem": exam.get('problem', {}),
"exam_info": {
"title": exam['title'],
"status": exam['status'],
"duration_minutes": exam['duration_minutes']
}
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/host-dashboard/<exam_code>", methods=["GET", "OPTIONS"])
def get_host_dashboard(exam_code):
"""Get comprehensive host dashboard data"""
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:
exam = db.exams.find_one({"exam_code": exam_code.upper()})
if not exam:
return jsonify({"error": "Exam not found"}), 404
participants = exam.get('participants', [])
# Separate participants by status
completed_participants = [p for p in participants if p.get('completed', False)]
waiting_participants = [p for p in participants if not p.get('completed', False)]
# Sort leaderboard
leaderboard = sorted(
completed_participants,
key=lambda x: (-x.get('score', 0), x.get('submission_time', datetime.now()))
)
# Add ranks
for i, participant in enumerate(leaderboard):
participant['rank'] = i + 1
# Calculate time statistics
current_time = datetime.now()
start_time = exam.get('start_time')
end_time = exam.get('end_time')
time_elapsed = 0
time_remaining = 0
if start_time:
time_elapsed = int((current_time - start_time).total_seconds())
if end_time and current_time < end_time:
time_remaining = int((end_time - current_time).total_seconds())
return jsonify({
"success": True,
"exam_info": {
"exam_code": exam['exam_code'],
"title": exam['title'],
"status": exam['status'],
"duration_minutes": exam['duration_minutes'],
"max_participants": exam.get('max_participants', 50),
"created_at": exam.get('created_at'),
"start_time": start_time,
"end_time": end_time,
"time_elapsed": time_elapsed,
"time_remaining": time_remaining
},
"participants": {
"total": len(participants),
"completed": len(completed_participants),
"working": len(waiting_participants),
"all_participants": sorted(participants, key=lambda x: x.get('joined_at', datetime.now())),
"recent_joins": sorted(participants, key=lambda x: x.get('joined_at', datetime.now()), reverse=True)[:5]
},
"leaderboard": leaderboard,
"statistics": {
"average_score": sum(p.get('score', 0) for p in completed_participants) / len(completed_participants) if completed_participants else 0,
"highest_score": max((p.get('score', 0) for p in completed_participants), default=0),
"lowest_score": min((p.get('score', 0) for p in completed_participants), default=0),
"completion_rate": (len(completed_participants) / len(participants) * 100) if participants else 0
},
"problem": exam.get('problem', {})
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp.route("/debug-join-data", methods=["POST", "OPTIONS"])
def debug_join_data():
"""Debug what data is actually being received"""
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
print(f"🔍 Raw request data: {request.data}")
print(f"🔍 Request JSON: {request.json}")
print(f"🔍 Content-Type: {request.headers.get('Content-Type')}")
return jsonify({
"received_raw": request.data.decode() if request.data else None,
"received_json": request.json,
"content_type": request.headers.get('Content-Type'),
"success": True
})
@bp.route("/test", methods=["GET"])
def test_exam_route():
"""Test if exam routes are working"""
return jsonify({
"success": True,
"message": "Exam routes are working",
"timestamp": datetime.now().isoformat(),
"available_routes": [
"/api/exam/create-exam",
"/api/exam/join-exam",
"/api/exam/start-exam",
"/api/exam/leaderboard/<exam_code>",
"/api/exam/get-problem/<exam_code>",
"/api/exam/host-dashboard/<exam_code>",
"/api/exam/debug-join-data"
]
})
@bp.route("/", methods=["GET"])
def exam_root():
"""Exam route root"""
return jsonify({
"message": "OpenLearnX Exam API",
"available_endpoints": [
"/api/exam/create-exam",
"/api/exam/join-exam",
"/api/exam/start-exam",
"/api/exam/leaderboard/<exam_code>",
"/api/exam/get-problem/<exam_code>",
"/api/exam/host-dashboard/<exam_code>",
"/api/exam/test",
"/api/exam/debug-join-data"
]
})
+42
View File
@@ -0,0 +1,42 @@
import docker
import tempfile
import os # ✅ Make sure this is imported
import subprocess
import time
from typing import Dict, List, Any
import json
class CompilerService:
def __init__(self):
self.client = docker.from_env()
self.language_configs = {
'python': {
'image': 'python:3.9-alpine',
'file_ext': '.py',
'run_command': 'python /app/solution{ext}',
'timeout': 10
},
'java': {
'image': 'openjdk:11-alpine',
'file_ext': '.java',
'run_command': 'cd /app && javac Solution.java && java Solution',
'timeout': 15
},
'c': {
'image': 'gcc:9-alpine',
'file_ext': '.c',
'run_command': 'cd /app && gcc -o solution solution.c && ./solution',
'timeout': 15
},
'bash': {
'image': 'bash:5-alpine',
'file_ext': '.sh',
'run_command': 'bash /app/solution.sh',
'timeout': 10
}
}
# ... rest of your compiler service code
# Global compiler service instance
compiler_service = CompilerService()
+305
View File
@@ -0,0 +1,305 @@
import docker
import tempfile
import os
import subprocess
import time
import uuid
import json
import threading
from typing import Dict, List, Any, Optional
from datetime import datetime
import queue
import signal
class RealCompilerService:
def __init__(self):
self.client = docker.from_env()
self.execution_queue = queue.Queue()
self.active_executions = {}
self.max_concurrent_executions = 5
# Enhanced language configurations with real execution
self.language_configs = {
'python': {
'image': 'python:3.11-slim',
'file_ext': '.py',
'compile_command': None, # Python doesn't need compilation
'run_command': 'python /app/code.py',
'timeout': 30,
'memory_limit': '256m',
'cpu_limit': '0.5'
},
'java': {
'image': 'openjdk:17-alpine',
'file_ext': '.java',
'compile_command': 'javac /app/Main.java',
'run_command': 'java -cp /app Main',
'timeout': 30,
'memory_limit': '512m',
'cpu_limit': '0.5'
},
'cpp': {
'image': 'gcc:latest',
'file_ext': '.cpp',
'compile_command': 'g++ -o /app/program /app/code.cpp -std=c++17',
'run_command': '/app/program',
'timeout': 30,
'memory_limit': '256m',
'cpu_limit': '0.5'
},
'c': {
'image': 'gcc:latest',
'file_ext': '.c',
'compile_command': 'gcc -o /app/program /app/code.c',
'run_command': '/app/program',
'timeout': 30,
'memory_limit': '256m',
'cpu_limit': '0.5'
},
'javascript': {
'image': 'node:18-alpine',
'file_ext': '.js',
'compile_command': None,
'run_command': 'node /app/code.js',
'timeout': 30,
'memory_limit': '256m',
'cpu_limit': '0.5'
},
'bash': {
'image': 'bash:5.2-alpine3.18',
'file_ext': '.sh',
'compile_command': None,
'run_command': 'bash /app/code.sh',
'timeout': 30,
'memory_limit': '128m',
'cpu_limit': '0.3'
},
'go': {
'image': 'golang:1.21-alpine',
'file_ext': '.go',
'compile_command': 'go build -o /app/program /app/code.go',
'run_command': '/app/program',
'timeout': 30,
'memory_limit': '512m',
'cpu_limit': '0.5'
},
'rust': {
'image': 'rust:1.75-alpine',
'file_ext': '.rs',
'compile_command': 'rustc /app/code.rs -o /app/program',
'run_command': '/app/program',
'timeout': 60, # Rust compilation can be slow
'memory_limit': '1g',
'cpu_limit': '1.0'
}
}
# Start execution worker
self.start_execution_worker()
def start_execution_worker(self):
"""Start background worker for code execution"""
def worker():
while True:
try:
execution_task = self.execution_queue.get(timeout=1)
self._execute_task(execution_task)
self.execution_queue.task_done()
except queue.Empty:
continue
except Exception as e:
print(f"Execution worker error: {e}")
worker_thread = threading.Thread(target=worker, daemon=True)
worker_thread.start()
def execute_code(self, code: str, language: str, input_data: str = "",
execution_id: str = None) -> Dict[str, Any]:
"""Execute code with real output capture"""
if language not in self.language_configs:
return {"error": f"Language '{language}' not supported"}
if not execution_id:
execution_id = str(uuid.uuid4())
config = self.language_configs[language]
try:
# Create execution context
execution_context = {
'execution_id': execution_id,
'code': code,
'language': language,
'input_data': input_data,
'config': config,
'start_time': datetime.now(),
'status': 'running'
}
self.active_executions[execution_id] = execution_context
# Execute in Docker container
result = self._execute_in_container(execution_context)
# Update execution context
execution_context['status'] = 'completed'
execution_context['end_time'] = datetime.now()
execution_context['result'] = result
return {
"success": True,
"execution_id": execution_id,
"output": result.get('output', ''),
"error": result.get('error', ''),
"execution_time": result.get('execution_time', 0),
"memory_used": result.get('memory_used', 0),
"exit_code": result.get('exit_code', 0),
"language": language,
"timestamp": datetime.now().isoformat()
}
except Exception as e:
return {
"error": f"Execution failed: {str(e)}",
"execution_id": execution_id,
"language": language
}
finally:
# Clean up
if execution_id in self.active_executions:
del self.active_executions[execution_id]
def _execute_in_container(self, context: Dict) -> Dict[str, Any]:
"""Execute code in secure Docker container"""
code = context['code']
language = context['language']
input_data = context['input_data']
config = context['config']
with tempfile.TemporaryDirectory() as temp_dir:
# Prepare code file
filename = f"code{config['file_ext']}" if language != 'java' else "Main.java"
file_path = os.path.join(temp_dir, filename)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(code)
# Prepare input file
input_file = os.path.join(temp_dir, 'input.txt')
with open(input_file, 'w', encoding='utf-8') as f:
f.write(input_data)
try:
start_time = time.time()
# Create and run container
container = self.client.containers.run(
config['image'],
command=self._build_execution_command(config, filename),
volumes={temp_dir: {'bind': '/app', 'mode': 'rw'}},
working_dir='/app',
mem_limit=config['memory_limit'],
cpu_period=100000,
cpu_quota=int(float(config['cpu_limit']) * 100000),
network_mode='none', # No network access
remove=True,
detach=False,
stdin_open=True,
tty=False,
timeout=config['timeout'],
# Security options
cap_drop=['ALL'],
security_opt=['no-new-privileges'],
read_only=False,
tmpfs={'/tmp': 'rw,noexec,nosuid,size=100m'}
)
execution_time = time.time() - start_time
output = container.decode('utf-8')
return {
"output": output.strip(),
"error": "",
"exit_code": 0,
"execution_time": round(execution_time, 3),
"memory_used": self._get_memory_usage(container)
}
except docker.errors.ContainerError as e:
return {
"output": "",
"error": f"Runtime error (exit code {e.exit_status}): {e.stderr.decode('utf-8') if e.stderr else 'Unknown error'}",
"exit_code": e.exit_status,
"execution_time": time.time() - start_time,
"memory_used": 0
}
except docker.errors.APIError as e:
return {
"output": "",
"error": f"Docker API error: {str(e)}",
"exit_code": -1,
"execution_time": 0,
"memory_used": 0
}
except Exception as e:
return {
"output": "",
"error": f"Execution error: {str(e)}",
"exit_code": -1,
"execution_time": 0,
"memory_used": 0
}
def _build_execution_command(self, config: Dict, filename: str) -> str:
"""Build the execution command for the container"""
commands = []
# Add compilation step if needed
if config.get('compile_command'):
commands.append(config['compile_command'])
# Add execution command with input redirection
run_cmd = config['run_command']
if '<' not in run_cmd: # Add input redirection if not present
run_cmd += ' < /app/input.txt 2>&1'
commands.append(run_cmd)
# Combine commands
return f"sh -c '{' && '.join(commands)}'"
def _get_memory_usage(self, container) -> int:
"""Get memory usage from container stats"""
try:
stats = container.stats(stream=False)
memory_usage = stats['memory']['usage']
return memory_usage
except:
return 0
def get_supported_languages(self) -> List[Dict[str, str]]:
"""Get list of supported languages with details"""
return [
{
'id': lang_id,
'name': lang_id.title(),
'extension': config['file_ext'],
'timeout': config['timeout'],
'memory_limit': config['memory_limit']
}
for lang_id, config in self.language_configs.items()
]
def get_execution_status(self, execution_id: str) -> Optional[Dict]:
"""Get status of a running execution"""
return self.active_executions.get(execution_id)
def cancel_execution(self, execution_id: str) -> bool:
"""Cancel a running execution"""
if execution_id in self.active_executions:
# Implementation would involve stopping the Docker container
del self.active_executions[execution_id]
return True
return False
# Create global instance
real_compiler_service = RealCompilerService()
+53
View File
@@ -0,0 +1,53 @@
from web3 import Web3
from eth_account import Account
from datetime import datetime
import hashlib
import secrets
import os # ✅ Add this missing import
class WalletService:
def __init__(self, web3_provider_url):
self.w3 = Web3(Web3.HTTPProvider(web3_provider_url))
def verify_wallet_signature(self, wallet_address, signature, message):
"""Verify wallet signature for authentication"""
try:
# Recover the address from signature
message_hash = Web3.keccak(text=message)
recovered_address = self.w3.eth.account.recover_message_hash(message_hash, signature=signature)
return recovered_address.lower() == wallet_address.lower()
except Exception as e:
print(f"Signature verification error: {e}")
return False
def generate_auth_message(self, wallet_address, exam_code):
"""Generate message for wallet signing"""
timestamp = int(datetime.now().timestamp())
nonce = secrets.token_hex(16)
message = f"""OpenLearnX Exam Authentication
Wallet: {wallet_address}
Exam Code: {exam_code}
Timestamp: {timestamp}
Nonce: {nonce}
Sign this message to join the coding exam."""
return message, timestamp, nonce
def create_wallet_session(self, wallet_address, exam_code, signature):
"""Create authenticated wallet session"""
session_id = hashlib.sha256(f"{wallet_address}{exam_code}{datetime.now().timestamp()}".encode()).hexdigest()
return {
"session_id": session_id,
"wallet_address": wallet_address,
"exam_code": exam_code,
"authenticated_at": datetime.now(),
"signature": signature
}
# Create the service instance
wallet_service = WalletService(os.getenv('WEB3_PROVIDER_URL', 'http://127.0.0.1:8545'))