initial commit blyad

This commit is contained in:
2025-08-11 21:49:33 +02:00
commit d342f888a9
14 changed files with 738 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
__pycache__
instance
.venv
*.sqlite3

1
__init__.py Normal file
View File

@@ -0,0 +1 @@
# This file marks the directory as a Python package.

89
app.py Normal file
View File

@@ -0,0 +1,89 @@
from flask import Flask, render_template, request, redirect, url_for
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
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
# Load problems from JSON at startup
json_path = os.path.join(os.path.dirname(__file__), 'problems.json')
load_problems_from_json(json_path)
# Schedule reload every 10 hours
schedule_problem_reload(app, json_path, interval_hours=10)
@app.route('/')
def index():
problems = Problem.query.all()
leaderboard = get_leaderboard()
# Map problem_id to title for leaderboard display
problem_titles = {p.id: p.title for p 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/<int:problem_id>', methods=['GET', 'POST'])
def view_problem(problem_id):
problem = Problem.query.get_or_404(problem_id)
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)
# Get the last line number in the user's code
if hasattr(tree, 'body') and tree.body:
last_node = tree.body[-1]
line_number = getattr(last_node, 'lineno', None)
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.id, run_result['runtime'], memory_used, line_number)
solution = Solution(problem_id=problem.id, user_code=user_code, passed=run_result['passed'], output=run_result['output'])
db.session.add(solution)
db.session.commit()
result = run_result
return render_template('problem.html', problem=problem, result=result)
if __name__ == '__main__':
app.run(debug=True)

46
leaderboard.py Normal file
View File

@@ -0,0 +1,46 @@
from flask_sqlalchemy import SQLAlchemy
from flask import g
import os
import sqlite3
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'db.sqlite3'))
return db
def create_leaderboard_table():
db = get_db()
cursor = db.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS leaderboard (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
problem_id INTEGER,
runtime REAL,
memory_used INTEGER,
line_number INTEGER,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
db.commit()
def log_leaderboard(username, problem_id, runtime, memory_used, line_number):
db = get_db()
cursor = db.cursor()
cursor.execute('''
INSERT INTO leaderboard (username, problem_id, runtime, memory_used, line_number)
VALUES (?, ?, ?, ?, ?)
''', (username, problem_id, runtime, memory_used, line_number))
db.commit()
def get_leaderboard():
db = get_db()
cursor = db.cursor()
cursor.execute('''
SELECT username, problem_id, runtime, memory_used, line_number, timestamp
FROM leaderboard
ORDER BY runtime ASC, memory_used ASC
LIMIT 20
''')
return cursor.fetchall()

17
models.py Normal file
View File

@@ -0,0 +1,17 @@
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Problem(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=False)
test_code = db.Column(db.Text, nullable=False) # Python code to test solution
class Solution(db.Model):
id = db.Column(db.Integer, primary_key=True)
problem_id = db.Column(db.Integer, db.ForeignKey('problem.id'), nullable=False)
user_code = db.Column(db.Text, nullable=False)
passed = db.Column(db.Boolean, default=False)
output = db.Column(db.Text)
problem = db.relationship('Problem', backref=db.backref('solutions', lazy=True))

37
problem_loader.py Normal file
View File

@@ -0,0 +1,37 @@
import json
import os
import threading
import time
from models import db, Problem
from flask import current_app
def load_problems_from_json(json_path):
if not os.path.exists(json_path):
print(f"Problem JSON file not found: {json_path}")
return
with open(json_path, 'r') as f:
problems = json.load(f)
for p in problems:
# Check if problem already exists by title
existing = Problem.query.filter_by(title=p['title']).first()
# Load test code from solution file if provided
test_code = ''
if 'solution' in p and os.path.exists(p['solution']):
with open(p['solution'], 'r') as sf:
test_code = sf.read()
if existing:
existing.description = p['description']
existing.test_code = test_code
else:
new_problem = Problem(title=p['title'], description=p['description'], test_code=test_code)
db.session.add(new_problem)
db.session.commit()
def schedule_problem_reload(app, json_path, interval_hours=10):
def reload_loop():
while True:
with app.app_context():
load_problems_from_json(json_path)
time.sleep(interval_hours * 3600)
t = threading.Thread(target=reload_loop, daemon=True)
t.start()

12
problems.json Normal file
View File

@@ -0,0 +1,12 @@
[
{
"title": "Reversed String",
"description": "Reverse a String with a Function (revstring); the function is supposed to take the string as an argument and is supposed to return the reversed string and print it.",
"solution": "problems/solution_reversed_string.py"
},
{
"title": "Sort List",
"description": "Sort a List with a Function (sortlist); the function is supposed to take the list as an argument and is supposed to return the sorted list and print it.",
"solution": "problems/sortlist.py"
}
]

View File

@@ -0,0 +1,14 @@
import unittest
#<!-- The Function the User needs to write -->
def revstring(x):
return x[::-1]
#<!-- This Test, test if the function works -->
class TestSolution(unittest.TestCase):
def test_simple(self):
x="";
self.assertEqual(revstring(x), x[::-1])
if __name__ == "__main__":
unittest.main()

11
problems/sortlist.py Normal file
View File

@@ -0,0 +1,11 @@
import unittest
class TestSolution(unittest.TestCase):
def test_sort(self):
# define x as a empty array.
# this will be used for the functiun ; a empty var does not work.
self.x = []
self.assertEqual(sortlist(self.x), sorted(self.x)) ## sort
if __name__ == "__main__":
unittest.main()

25
readme.md Normal file
View File

@@ -0,0 +1,25 @@
## under construction
### Like LeetCode
but more lightweight
if you want to contribute write tests like this:
```python
import unittest
#<!-- The Function the User needs to write -->
def revstring(x):
return x[::-1]
#<!-- This Test, test if the function works -->
class TestSolution(unittest.TestCase):
def test_simple(self):
# !! This needs to be dynamic ; if the user enters some shit then it is supposed to work too
x="";
self.assertEqual(revstring(x), x[::-1])
if __name__ == "__main__":
unittest.main()
```

260
static/style.css Normal file
View File

@@ -0,0 +1,260 @@
/* Reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8f9fa;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
/* Main heading */
h1 {
color: #2c3e50;
margin-bottom: -10px;
padding-bottom: 3px;
border-bottom: 3px solid #3498db;
font-size: 2.2em;
}
h2 {
color: #34495e;
margin: 30px 0 20px 0;
font-size: 1.5em;
}
h3 {
color: #34495e;
margin: 25px 0 15px 0;
font-size: 1.3em;
}
/* Links and buttons */
a {
color: #3498db;
text-decoration: none;
padding: 8px 16px;
border-radius: 5px;
transition: background-color 0.3s ease;
}
a:hover {
background-color: #e3f2fd;
text-decoration: none;
}
/* Primary action link (Submit New Problem) */
a[href="/problem/new"] {
background-color: #3498db;
color: white;
font-weight: 600;
margin-bottom: 30px;
display: inline-block;
padding: 12px 24px;
border-radius: 8px;
}
a[href="/problem/new"]:hover {
background-color: #2980b9;
}
/* Problem list */
ul {
list-style: none;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 25px;
margin: 20px 0;
}
li {
padding: 15px 0;
border-bottom: 1px solid #eee;
}
li:last-child {
border-bottom: none;
}
li a {
display: block;
padding: 12px 20px;
margin: -12px -20px;
border-radius: 6px;
font-size: 1.1em;
}
li a:hover {
background-color: #f8f9fa;
transform: translateX(5px);
transition: all 0.2s ease;
}
/* Problem page specific styles */
.problem-header {
display: flex;
align-items: center;
margin-bottom: 30px;
gap: 20px;
}
.back-btn {
background-color: #95a5a6;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background-color 0.3s ease;
}
.back-btn:hover {
background-color: #7f8c8d;
}
.problem-desc {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 30px;
font-size: 1.1em;
line-height: 1.7;
}
/* Editor section */
.editor-section {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
#editor {
border: 2px solid #ddd;
border-radius: 8px;
margin: 20px 0;
height: 400px;
overflow: hidden;
}
.editor-actions {
margin-top: 20px;
text-align: right;
}
form button[type="submit"] {
background-color: #27ae60;
color: white;
border: none;
padding: 12px 30px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 600;
transition: background-color 0.3s ease;
}
form button[type="submit"]:hover {
background-color: #229954;
}
/* Results section */
b {
color: #2c3e50;
display: inline-block;
margin: 10px 0 5px 0;
}
pre {
background-color: #f4f4f4;
padding: 20px;
border-radius: 6px;
border-left: 4px solid #3498db;
margin: 10px 0 20px 0;
overflow-x: auto;
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 14px;
line-height: 1.4;
}
pre[style*="color:red"] {
border-left-color: #e74c3c;
background-color: #fdf2f2;
}
/* Status messages */
p[style*="color:green"] {
background-color: #d4edda;
color: #155724;
padding: 15px 20px;
border-radius: 6px;
border-left: 4px solid #27ae60;
margin: 20px 0;
font-weight: 600;
}
p[style*="color:red"] {
background-color: #f8d7da;
color: #721c24;
padding: 15px 20px;
border-radius: 6px;
border-left: 4px solid #e74c3c;
margin: 20px 0;
font-weight: 600;
}
/* Back to Problems link */
a[href="/"] {
display: inline-block;
margin-top: 30px;
background-color: #6c757d;
color: white;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
}
a[href="/"]:hover {
background-color: #5a6268;
}
/* Responsive design */
@media (max-width: 768px) {
body {
padding: 15px;
}
.problem-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
h1 {
font-size: 1.8em;
}
.problem-desc, .editor-section, ul {
padding: 20px;
}
#editor {
height: 300px;
}
.editor-actions {
text-align: center;
}
}

99
templates/index.html Normal file
View File

@@ -0,0 +1,99 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quick Problem Platform</title>
<link rel="stylesheet" href="/static/style.css">
<style>
.leaderboard-table {
width: auto;
min-width: 400px;
max-width: 600px;
border-collapse: collapse;
margin: 2em 0;
background: #fff;
box-shadow: 0 2px 8px #0001;
border-radius: 8px;
overflow: hidden;
}
.leaderboard-table th, .leaderboard-table td {
padding: 0.7em 1em;
text-align: center;
}
.leaderboard-table th {
background: #f5f5f5;
font-weight: 600;
color: #333;
}
.leaderboard-table tr:nth-child(even) {
background: #fafbfc;
}
.leaderboard-table tr:nth-child(odd) {
background: #f0f2f5;
}
.leaderboard-table td {
font-family: 'JetBrains Mono', monospace;
font-size: 1em;
}
.leaderboard-table tr:hover {
background: #e0e7ff;
}
.problems-list {
max-width: 600px;
min-width: 400px;
margin: 0;
padding: 0;
}
.problems-list ul {
list-style: none;
padding: 0;
margin: 0;
}
.problems-list li {
padding: 0.5em 0;
}
</style>
</head>
<body>
<h1>Quick Problem Platform</h1>
<!--<a href="/problem/new">Submit New Problem</a>-->
<section class="problems-list">
<h2>Problems</h2>
<ul>
{% for problem in problems %}
<li><a href="/problem/{{ problem.id }}">{{ problem.title }}</a></li>
{% else %}
<li>No problems yet.</li>
{% endfor %}
</ul>
</section>
<section>
<h2>Leaderboard</h2>
<table class="leaderboard-table">
<tr>
<th>Rank</th>
<th>User</th>
<th>Problem</th>
<th>Runtime (s)</th>
<th>Memory (KB)</th>
<th>Line Number</th>
<th>Timestamp</th>
</tr>
{% for entry in leaderboard %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ entry[0] }}</td>
<td>{{ problem_titles.get(entry[1], 'Unknown') }}</td>
<td>{{ '%.4f'|format(entry[2]) }}</td>
<td>{{ entry[3] }}</td>
<td>{{ entry[4] if entry[4] else '-' }}</td>
<td>{{ entry[5] }}</td>
</tr>
{% else %}
<tr><td colspan="7">No leaderboard entries yet.</td></tr>
{% endfor %}
</table>
</section>
</body>
</html>

72
templates/problem.html Normal file
View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ problem.title }}</title>
<link rel="stylesheet" href="/static/style.css">
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
</head>
<body>
<div class="problem-header">
<button class="back-btn" onclick="window.location.href='/'">← Back</button>
<h1 style="margin-bottom:0;">{{ problem.title }}</h1>
</div>
<div class="problem-desc">{{ problem.description }}</div>
<div class="editor-section" style="max-width:1160;margin:0">
<h2 style="margin-top:0;">Submit Your Solution (Python)</h2>
<form method="post">
<label for="username">Username (optional):</label>
<input type="text" name="username" id="username" placeholder="Anonymous" style="margin-bottom:10px;">
<div id="editor"></div>
<textarea name="user_code" id="user_code" style="display:none;"></textarea>
<div class="editor-actions">
<button type="submit">Run & Submit</button>
</div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' } });
require(['vs/editor/editor.main'], function() {
var editor = monaco.editor.create(document.getElementById('editor'), {
value: '',
language: 'python',
theme: 'vs-light',
fontFamily: 'JetBrains Mono, monospace',
fontLigatures: true,
automaticLayout: true,
fontSize: 16,
minimap: { enabled: false }
});
document.querySelector('form').addEventListener('submit', function(e) {
var code = editor.getValue();
if (!code.trim()) {
alert('Please enter your code before submitting.');
e.preventDefault();
return false;
}
document.getElementById('user_code').value = code;
});
});
</script>
{% if result %}
<div class="editor-section" style="max-width:1160;margin:0;margin-top: 5px;">
<h3>Result:</h3>
<b>Runtime:</b> {{ '%.4f'|format(result.runtime) }} seconds<br>
<b>Output:</b>
<pre>{{ result.output }}</pre>
{% if result.error %}
<b>Error:</b>
<pre style="color:red;">{{ result.error }}</pre>
{% endif %}
{% if result.passed %}
<p style="color:green;">Passed!</p>
{% else %}
<p style="color:red;">Failed!</p>
{% endif %}
</div>
{% endif %}
<!--<a href="/">Back to Problems</a>-->
</body>
</html>

51
utils.py Normal file
View File

@@ -0,0 +1,51 @@
import sys
import traceback
import time
import io
def run_code_against_tests(user_code, test_code):
import tempfile
import subprocess
local_ns = {}
output = ''
start = time.perf_counter()
error = None
passed = False
try:
# Check if unittest is used in test_code
if 'unittest' in test_code:
# Write user code + test code to a temp file
with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False) as f:
f.write(user_code + '\n' + test_code)
f.flush()
f_name = f.name
# Run the file as a subprocess
proc = subprocess.run([sys.executable, f_name], capture_output=True, text=True, timeout=10)
output = proc.stdout + proc.stderr
passed = proc.returncode == 0
error = None if passed else output
else:
# Capture stdout
old_stdout = sys.stdout
sys.stdout = mystdout = io.StringIO()
# Execute user code
exec(user_code, {}, local_ns)
# Execute test code (should raise AssertionError if fail)
exec(test_code, local_ns, local_ns)
passed = True
except Exception as e:
passed = False
error = traceback.format_exc()
finally:
if 'mystdout' in locals():
output = mystdout.getvalue() or output
sys.stdout = old_stdout if 'old_stdout' in locals() else sys.stdout
runtime = time.perf_counter() - start
result = {
'passed': passed,
'output': output,
'runtime': runtime,
'error': error if not passed else None
}
return result