""" Code compilation route. """ import os import time import uuid import requests from flask import Blueprint, request, jsonify from flask_limiter import RateLimitExceeded from services.token_service import validate_token from extensions import limiter compile_bp = Blueprint('compile', __name__) COMPILER_WORKER_URL = os.environ.get('COMPILER_WORKER_URL', 'http://compiler-worker:8080/execute') VELXIO_COMPILER_URL = os.environ.get('VELXIO_COMPILER_URL', 'http://velxio:80/api/compile/') ANON_QUEUE_DIR = "/tmp/elemes_anon_queue" # Ensure queue directory exists os.makedirs(ANON_QUEUE_DIR, exist_ok=True) def is_logged_in(): """Check if the request has a valid student token (JSON, Form, or Cookie).""" token = None if request.is_json: try: token = request.get_json(force=True).get('token', '') except Exception: pass if not token: token = request.form.get('token', '') if not token: token = request.cookies.get('student_token', '') token = str(token).strip() return bool(token and validate_token(token)) def acquire_anon_slot(): """Try to acquire a slot in the anonymous compilation queue.""" try: # Cleanup stale files (> 60s) now = time.time() for f in os.listdir(ANON_QUEUE_DIR): fpath = os.path.join(ANON_QUEUE_DIR, f) try: if now - os.path.getmtime(fpath) > 60: os.remove(fpath) except (OSError, IOError): pass # Check capacity if len(os.listdir(ANON_QUEUE_DIR)) >= 20: return None slot_id = str(uuid.uuid4()) fpath = os.path.join(ANON_QUEUE_DIR, slot_id) with open(fpath, 'w') as f: f.write(str(now)) return fpath except Exception: return None def release_anon_slot(fpath): """Release an acquired anonymous slot.""" if fpath and os.path.exists(fpath): try: os.remove(fpath) except (OSError, IOError): pass @compile_bp.errorhandler(RateLimitExceeded) def handle_rate_limit_exceeded(e): """Handle rate limit errors with a custom message.""" return jsonify({ 'success': False, 'output': '', 'error': 'tunggu beberapa saat untuk kompilasi kembali, atau lakukan request token ke guru' }), 429 @compile_bp.route('/compile', methods=['POST']) @limiter.limit("1 per 2 minutes", exempt_when=is_logged_in) def compile_code(): """Forward code compilation to the worker service.""" try: code = None language = None token = None if request.content_type and 'application/json' in request.content_type: try: json_data = request.get_json(force=True) if json_data: code = json_data.get('code', '') language = json_data.get('language', '') token = json_data.get('token', '').strip() except Exception: pass if not code: code = request.form.get('code', '') language = request.form.get('language', '') token = request.form.get('token', '').strip() # Authorization logic: # 1. If token is provided, it MUST be valid. # 2. If no token, it is anonymous access (subject to rate limits & queue). user_is_logged_in = False if token: if not validate_token(token): return jsonify({'success': False, 'output': '', 'error': 'Unauthorized: Invalid token'}) user_is_logged_in = True # Queue logic for anonymous users slot_fpath = None if not user_is_logged_in: slot_fpath = acquire_anon_slot() if not slot_fpath: return jsonify({ 'success': False, 'output': '', 'error': 'tunggu beberapa saat untuk kompilasi kembali, atau lakukan request token ke guru' }) try: if not code: return jsonify({'success': False, 'output': '', 'error': 'No code provided'}) # Forward to worker response = requests.post( COMPILER_WORKER_URL, json={'code': code, 'language': language}, timeout=15 ) return jsonify(response.json()) finally: if slot_fpath: release_anon_slot(slot_fpath) except requests.exceptions.RequestException as re: return jsonify({'success': False, 'output': '', 'error': f'Compiler service unavailable: {re}'}) except Exception as e: return jsonify({'success': False, 'output': '', 'error': f'An error occurred: {e}'}) @compile_bp.route('/velxio-compile', methods=['POST']) @compile_bp.route('/velxio-compile/', methods=['POST']) @limiter.limit("1 per 2 minutes", exempt_when=is_logged_in) def velxio_compile(): """Proxy Velxio compilation requests to the Velxio service.""" slot_fpath = None try: # Check authorization from cookie if not in JSON token = request.cookies.get('student_token') user_is_logged_in = bool(token and validate_token(token)) if not user_is_logged_in: slot_fpath = acquire_anon_slot() if not slot_fpath: return jsonify({ 'success': False, 'output': '', 'error': 'tunggu beberapa saat untuk kompilasi kembali, atau lakukan request token ke guru' }) # Forward the request to Velxio response = requests.post( VELXIO_COMPILER_URL, json=request.get_json(silent=True) or {}, timeout=30 ) return jsonify(response.json()) except requests.exceptions.RequestException as re: return jsonify({'success': False, 'output': '', 'error': f'Velxio service unavailable: {re}'}) except Exception as e: return jsonify({'success': False, 'output': '', 'error': f'An error occurred: {e}'}) finally: if slot_fpath: release_anon_slot(slot_fpath)