from markupsafe import Markup from flask import Flask, render_template, request, redirect, url_for, send_from_directory import markdown as md from models import db, Problem, Solution from utils import run_code_against_tests from leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard import os ## from problem_loader import load_problems_from_json, schedule_problem_reload from problem_scanner import start_problem_scanner import sqlite3 from pathlib import Path app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' db.init_app(app) @app.before_request def setup(): db.create_all() create_leaderboard_table() # Ensure leaderboard table exists # Problems are now loaded from manifests by the background scanner. No need to load problems.json. # Start the background thread to scan problems start_problem_scanner() @app.route("/script.js") def script(): return send_from_directory("templates", "script.js") @app.route('/favicon.ico') def favicon(): return send_from_directory("templates", "favicon", "favicon.ico") @app.route('/') def index(): db_path = Path(__file__).parent / 'problems.sqlite3' conn = sqlite3.connect(db_path) c = conn.cursor() c.execute('SELECT folder, description, test_code FROM problems') problems = c.fetchall() conn.close() # Get leaderboard entries leaderboard = get_leaderboard() # Map folder to title for display problem_titles = {folder: folder.replace('_', ' ').title() for folder, _, _ in problems} return render_template('index.html', problems=problems, leaderboard=leaderboard, problem_titles=problem_titles) @app.route('/problem/new', methods=['GET', 'POST']) def new_problem(): if request.method == 'POST': title = request.form['title'] description = request.form['description'] test_code = request.form['test_code'] problem = Problem(title=title, description=description, test_code=test_code) db.session.add(problem) db.session.commit() return redirect(url_for('index')) return render_template('new_problem.html') @app.route('/problem/', methods=['GET', 'POST']) def view_problem(folder): db_path = Path(__file__).parent / 'problems.sqlite3' conn = sqlite3.connect(db_path) c = conn.cursor() c.execute('SELECT folder, description, test_code FROM problems WHERE folder = ?', (folder,)) row = c.fetchone() conn.close() if not row: return 'Problem not found', 404 problem = { 'folder': row[0], 'description': row[1], 'test_code': row[2], 'title': row[0].replace('_', ' ').title() } result = None if request.method == 'POST': user_code = request.form['user_code'] username = request.form.get('username', '').strip() or 'Anonymous' import tracemalloc tracemalloc.start() run_result = run_code_against_tests(user_code, problem['test_code']) current, peak = tracemalloc.get_traced_memory() tracemalloc.stop() memory_used = peak // 1024 # in KB # Try to get the last line number executed (even for successful runs) line_number = None import ast try: tree = ast.parse(user_code) # Find the highest line number in the AST (for multi-function/user code) def get_max_lineno(node): max_lineno = getattr(node, 'lineno', 0) for child in ast.iter_child_nodes(node): max_lineno = max(max_lineno, get_max_lineno(child)) return max_lineno line_number = get_max_lineno(tree) except Exception: pass # If there was an error, try to get the error line number from the traceback if run_result['error']: tb = run_result['error'] import traceback try: tb_lines = traceback.extract_tb(traceback.TracebackException.from_string(tb).stack) if tb_lines: line_number = tb_lines[-1].lineno except Exception: pass log_leaderboard(username, problem['folder'], run_result['runtime'], memory_used, line_number) result = run_result return render_template('problem.html', problem=problem, result=result) @app.template_filter('markdown') def markdown_filter(text): return Markup(md.markdown(text or '', extensions=['extra', 'sane_lists'])) if __name__ == '__main__': app.run(debug=True)