From 89ea87951ea362daec10b73c340c0584910bdd03 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Tue, 12 Aug 2025 20:16:46 +0200 Subject: [PATCH] made this a hell of a lot better --- .gitignore | 1 + __init__.py => QPP/__init__.py | 0 app.py => QPP/app.py | 30 +- leaderboard.py => QPP/leaderboard.py | 10 +- models.py => QPP/models.py | 0 problem_loader.py => QPP/problem_loader.py | 0 problem_scanner.py => QPP/problem_scanner.py | 2 +- .../problems}/fibonacisequence/description.md | 0 .../problems}/fibonacisequence/manifest.json | 0 .../problems}/fibonacisequence/test.py | 0 .../problems}/reversedstring/description.md | 0 .../problems}/reversedstring/manifest.json | 0 QPP/problems/reversedstring/test.py | 26 ++ .../problems}/sortlist/description.md | 0 QPP/problems/sortlist/manifets.json | 7 + {problems => QPP/problems}/sortlist/test.py | 0 {static => QPP/static}/index.css | 0 {static => QPP/static}/style.css | 0 .../templates}/favicon/favicon.ico | Bin QPP/templates/index.html | 102 ++++++ QPP/templates/problem.html | 296 ++++++++++++++++++ {templates => QPP/templates}/script.js | 0 utils.py => QPP/utils.py | 0 problems/reversedstring/test.py | 26 -- problems/sortlist/manifets.json | 7 - requirements.txt | 5 + run.bash | 14 + templates/index.html | 102 ------ templates/problem.html | 296 ------------------ 29 files changed, 474 insertions(+), 450 deletions(-) rename __init__.py => QPP/__init__.py (100%) rename app.py => QPP/app.py (86%) rename leaderboard.py => QPP/leaderboard.py (82%) rename models.py => QPP/models.py (100%) rename problem_loader.py => QPP/problem_loader.py (100%) rename problem_scanner.py => QPP/problem_scanner.py (99%) rename {problems => QPP/problems}/fibonacisequence/description.md (100%) rename {problems => QPP/problems}/fibonacisequence/manifest.json (100%) rename {problems => QPP/problems}/fibonacisequence/test.py (100%) rename {problems => QPP/problems}/reversedstring/description.md (100%) rename {problems => QPP/problems}/reversedstring/manifest.json (100%) create mode 100644 QPP/problems/reversedstring/test.py rename {problems => QPP/problems}/sortlist/description.md (100%) create mode 100644 QPP/problems/sortlist/manifets.json rename {problems => QPP/problems}/sortlist/test.py (100%) rename {static => QPP/static}/index.css (100%) rename {static => QPP/static}/style.css (100%) rename {templates => QPP/templates}/favicon/favicon.ico (100%) create mode 100644 QPP/templates/index.html create mode 100644 QPP/templates/problem.html rename {templates => QPP/templates}/script.js (100%) rename utils.py => QPP/utils.py (100%) create mode 100644 requirements.txt create mode 100644 run.bash diff --git a/.gitignore b/.gitignore index 97f71e4..2e46365 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ __pycache__ instance .venv +venv *.sqlite3 \ No newline at end of file diff --git a/__init__.py b/QPP/__init__.py similarity index 100% rename from __init__.py rename to QPP/__init__.py diff --git a/app.py b/QPP/app.py similarity index 86% rename from app.py rename to QPP/app.py index 6a51732..7685961 100644 --- a/app.py +++ b/QPP/app.py @@ -1,24 +1,27 @@ 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 ast +from QPP.models import db, Problem, Solution +from QPP.utils import run_code_against_tests +from QPP.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 +from QPP.problem_scanner import start_problem_scanner import sqlite3 from pathlib import Path app = Flask(__name__) -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' + +BASE_DIR = Path(__file__).parent +app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{BASE_DIR / 'database' / 'db.sqlite3'}" + +print(f">>>>>>>>>>>>>>>>>>>>< Using database URI: {app.config['SQLALCHEMY_DATABASE_URI']}") + db.init_app(app) - - @app.before_request def setup(): db.create_all() @@ -38,7 +41,7 @@ def favicon(): @app.route('/') def index(): - db_path = Path(__file__).parent / 'problems.sqlite3' + db_path = Path(__file__).parent / 'database/problems.sqlite3' conn = sqlite3.connect(db_path) c = conn.cursor() # @@ -65,7 +68,7 @@ def new_problem(): @app.route('/problem/', methods=['GET', 'POST']) def view_problem(folder): - db_path = Path(__file__).parent / 'problems.sqlite3' + db_path = Path(__file__).parent / 'database/problems.sqlite3' conn = sqlite3.connect(db_path) c = conn.cursor() c.execute('SELECT folder, description,test_code , difficulty FROM problems WHERE folder = ?', (folder,)) @@ -78,11 +81,11 @@ def view_problem(folder): problem = { 'folder': row[0], 'description': row[1], - 'difficulty': row[2], - 'test_code': row[3], # This is used internally, not displayed - 'title': row[0].replace('_', ' ').title() + 'difficulty': row[3], # now correct + 'test_code': row[2], # now correct } + result = None if request.method == 'POST': user_code = request.form['user_code'] @@ -95,7 +98,6 @@ def view_problem(folder): 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) diff --git a/leaderboard.py b/QPP/leaderboard.py similarity index 82% rename from leaderboard.py rename to QPP/leaderboard.py index 6f8516e..66f3b6a 100644 --- a/leaderboard.py +++ b/QPP/leaderboard.py @@ -2,12 +2,14 @@ from flask_sqlalchemy import SQLAlchemy from flask import g import os import sqlite3 +from pathlib import Path 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 + if 'db' not in g: + db_path = Path(__file__).parent / 'database' / 'db.sqlite3' + db_path.parent.mkdir(exist_ok=True) # Ensure /database folder exists + g.db = sqlite3.connect(db_path) + return g.db def create_leaderboard_table(): db = get_db() diff --git a/models.py b/QPP/models.py similarity index 100% rename from models.py rename to QPP/models.py diff --git a/problem_loader.py b/QPP/problem_loader.py similarity index 100% rename from problem_loader.py rename to QPP/problem_loader.py diff --git a/problem_scanner.py b/QPP/problem_scanner.py similarity index 99% rename from problem_scanner.py rename to QPP/problem_scanner.py index e3b7dff..47af8e0 100644 --- a/problem_scanner.py +++ b/QPP/problem_scanner.py @@ -19,7 +19,7 @@ except ImportError: WATCHDOG_AVAILABLE = False PROBLEMS_DIR = Path(__file__).parent / 'problems' -DB_PATH = Path(__file__).parent / 'problems.sqlite3' +DB_PATH = Path(__file__).parent / 'database/problems.sqlite3' class ProblemScannerThread(threading.Thread): def __init__(self, scan_interval=2): diff --git a/problems/fibonacisequence/description.md b/QPP/problems/fibonacisequence/description.md similarity index 100% rename from problems/fibonacisequence/description.md rename to QPP/problems/fibonacisequence/description.md diff --git a/problems/fibonacisequence/manifest.json b/QPP/problems/fibonacisequence/manifest.json similarity index 100% rename from problems/fibonacisequence/manifest.json rename to QPP/problems/fibonacisequence/manifest.json diff --git a/problems/fibonacisequence/test.py b/QPP/problems/fibonacisequence/test.py similarity index 100% rename from problems/fibonacisequence/test.py rename to QPP/problems/fibonacisequence/test.py diff --git a/problems/reversedstring/description.md b/QPP/problems/reversedstring/description.md similarity index 100% rename from problems/reversedstring/description.md rename to QPP/problems/reversedstring/description.md diff --git a/problems/reversedstring/manifest.json b/QPP/problems/reversedstring/manifest.json similarity index 100% rename from problems/reversedstring/manifest.json rename to QPP/problems/reversedstring/manifest.json diff --git a/QPP/problems/reversedstring/test.py b/QPP/problems/reversedstring/test.py new file mode 100644 index 0000000..da68ff1 --- /dev/null +++ b/QPP/problems/reversedstring/test.py @@ -0,0 +1,26 @@ +import unittest + +class TestSolution(unittest.TestCase): + def test_simple(self): + test_cases = [ + ("Hello World", "dlroW olleH"), + ("", ""), + ("a", "a"), + ("racecar", "racecar"), + ("12345", "54321"), + ("!@# $%", "%$ #@!") + ] + + print("\n=== Function Output Test Results ===") + for input_val, expected in test_cases: + try: + actual = revstring(input_val) # pyright: ignore[reportUndefinedVariable] + status = "✓ PASS" if actual == expected else "✗ FAIL" + print(f"{status} | Input: '{input_val}' -> Got: '{actual}' | Expected: '{expected}'") + self.assertEqual(actual, expected) + except Exception as e: + print(f"✗ ERROR | Input: '{input_val}' -> Exception: {e}") + raise + +if __name__ == "__main__": + unittest.main(verbosity=2) \ No newline at end of file diff --git a/problems/sortlist/description.md b/QPP/problems/sortlist/description.md similarity index 100% rename from problems/sortlist/description.md rename to QPP/problems/sortlist/description.md diff --git a/QPP/problems/sortlist/manifets.json b/QPP/problems/sortlist/manifets.json new file mode 100644 index 0000000..a8920fb --- /dev/null +++ b/QPP/problems/sortlist/manifets.json @@ -0,0 +1,7 @@ +{ + "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.", + "description_md": "problems/sortlist/description.md", + "difficulty": "easy", + "test_code": "problems/sortlist/test.py" +} \ No newline at end of file diff --git a/problems/sortlist/test.py b/QPP/problems/sortlist/test.py similarity index 100% rename from problems/sortlist/test.py rename to QPP/problems/sortlist/test.py diff --git a/static/index.css b/QPP/static/index.css similarity index 100% rename from static/index.css rename to QPP/static/index.css diff --git a/static/style.css b/QPP/static/style.css similarity index 100% rename from static/style.css rename to QPP/static/style.css diff --git a/templates/favicon/favicon.ico b/QPP/templates/favicon/favicon.ico similarity index 100% rename from templates/favicon/favicon.ico rename to QPP/templates/favicon/favicon.ico diff --git a/QPP/templates/index.html b/QPP/templates/index.html new file mode 100644 index 0000000..7d241c2 --- /dev/null +++ b/QPP/templates/index.html @@ -0,0 +1,102 @@ + + + + + + Quick Problem Platform + + + + + + +
+
+

Quick Problem Platform

+
+
+ +
+
+ +
+

Problems

+
+ {% for folder, description, test_code, difficulty in problems %} +
+ {{ folder.replace('_',' ').title() }} + {{ difficulty }} +
+ {% else %} +
No problems yet.
+ {% endfor %} +
+
+ + +
+
+

Leaderboard

+ +
+
+ + + +
+
+ + + + + + + + + + + + + + {% for entry in leaderboard %} + + + + + + + + + + {% else %} + + {% endfor %} + +
RankUserProblemRuntime (s)Memory (KB)LineTimestamp
{{ loop.index }}{{ entry[0] }}{{ problem_titles.get(entry[1], 'Unknown') }}{{ '%.4f'|format(entry[2]) }}{{ entry[3] }}{{ entry[4] if entry[4] else '-' }}{{ entry[5] }}
No leaderboard entries yet.
+
+
+
+
+ + diff --git a/QPP/templates/problem.html b/QPP/templates/problem.html new file mode 100644 index 0000000..80aea3a --- /dev/null +++ b/QPP/templates/problem.html @@ -0,0 +1,296 @@ + + + + + + {{ problem.title }} - Coding Problem + + + + + + +
+
+
+ +

{{ problem.title }}

+
+
{{ problem.description | safe | markdown }}
+
+ +
+
+

Submit Your Solution (Python)

+
+
+
+ + +
+ +
+ +
+
+ +
+

Result

+ {% if result %} +

Runtime: {{ '%.4f'|format(result.runtime) }} seconds

+

Output:

+
{{ result.output }}
+ {% if result.error %} +

Error:

+
{{ result.error }}
+ {% endif %} + {% else %} +
+ Your code execution results will appear here +
+ {% endif %} +
+
+
+
+ + + + + \ No newline at end of file diff --git a/templates/script.js b/QPP/templates/script.js similarity index 100% rename from templates/script.js rename to QPP/templates/script.js diff --git a/utils.py b/QPP/utils.py similarity index 100% rename from utils.py rename to QPP/utils.py diff --git a/problems/reversedstring/test.py b/problems/reversedstring/test.py index da68ff1..e69de29 100644 --- a/problems/reversedstring/test.py +++ b/problems/reversedstring/test.py @@ -1,26 +0,0 @@ -import unittest - -class TestSolution(unittest.TestCase): - def test_simple(self): - test_cases = [ - ("Hello World", "dlroW olleH"), - ("", ""), - ("a", "a"), - ("racecar", "racecar"), - ("12345", "54321"), - ("!@# $%", "%$ #@!") - ] - - print("\n=== Function Output Test Results ===") - for input_val, expected in test_cases: - try: - actual = revstring(input_val) # pyright: ignore[reportUndefinedVariable] - status = "✓ PASS" if actual == expected else "✗ FAIL" - print(f"{status} | Input: '{input_val}' -> Got: '{actual}' | Expected: '{expected}'") - self.assertEqual(actual, expected) - except Exception as e: - print(f"✗ ERROR | Input: '{input_val}' -> Exception: {e}") - raise - -if __name__ == "__main__": - unittest.main(verbosity=2) \ No newline at end of file diff --git a/problems/sortlist/manifets.json b/problems/sortlist/manifets.json index a8920fb..e69de29 100644 --- a/problems/sortlist/manifets.json +++ b/problems/sortlist/manifets.json @@ -1,7 +0,0 @@ -{ - "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.", - "description_md": "problems/sortlist/description.md", - "difficulty": "easy", - "test_code": "problems/sortlist/test.py" -} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4d9b37d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Flask>=3.0 +Flask-SQLAlchemy>=3.1 +Markdown>=3.6 +MarkupSafe>=2.1 +watchdog>=4.0 \ No newline at end of file diff --git a/run.bash b/run.bash new file mode 100644 index 0000000..40a15ce --- /dev/null +++ b/run.bash @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e # exit if any command fails + +python -m venv venv +source venv/bin/activate + +pip install --upgrade pip +pip install -r requirements.txt + +export FLASK_APP=QPP.app +export FLASK_ENV=production + +flask run --host=0.0.0.0 --port=5000 diff --git a/templates/index.html b/templates/index.html index 7d241c2..e69de29 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,102 +0,0 @@ - - - - - - Quick Problem Platform - - - - - - -
-
-

Quick Problem Platform

-
-
- -
-
- -
-

Problems

-
- {% for folder, description, test_code, difficulty in problems %} -
- {{ folder.replace('_',' ').title() }} - {{ difficulty }} -
- {% else %} -
No problems yet.
- {% endfor %} -
-
- - -
-
-

Leaderboard

- -
-
- - - -
-
- - - - - - - - - - - - - - {% for entry in leaderboard %} - - - - - - - - - - {% else %} - - {% endfor %} - -
RankUserProblemRuntime (s)Memory (KB)LineTimestamp
{{ loop.index }}{{ entry[0] }}{{ problem_titles.get(entry[1], 'Unknown') }}{{ '%.4f'|format(entry[2]) }}{{ entry[3] }}{{ entry[4] if entry[4] else '-' }}{{ entry[5] }}
No leaderboard entries yet.
-
-
-
-
- - diff --git a/templates/problem.html b/templates/problem.html index 80aea3a..e69de29 100644 --- a/templates/problem.html +++ b/templates/problem.html @@ -1,296 +0,0 @@ - - - - - - {{ problem.title }} - Coding Problem - - - - - - -
-
-
- -

{{ problem.title }}

-
-
{{ problem.description | safe | markdown }}
-
- -
-
-

Submit Your Solution (Python)

-
-
-
- - -
- -
- -
-
- -
-

Result

- {% if result %} -

Runtime: {{ '%.4f'|format(result.runtime) }} seconds

-

Output:

-
{{ result.output }}
- {% if result.error %} -

Error:

-
{{ result.error }}
- {% endif %} - {% else %} -
- Your code execution results will appear here -
- {% endif %} -
-
-
-
- - - - - \ No newline at end of file