from flask import Flask, abort, render_template, request import datetime import json import sqlite3 import subprocess import config app = Flask(__name__) DEFAULT_CATEGORIES = [ {"name": "linux commands", "items": ["cd", "touch", "cat", "find"], "level": 1}, {"name": "types of HTML input", "items": ["radio", "password", "submit", "text"], "level": 2}, {"name": "sticky ___", "items": ["tape", "fingers", "situation", "wicket"], "level": 3}, {"name": "how i feel, spelled backwards", "items": ["live", "deliver", "desserts", "denier"], "level": 4}, ] def get_puzzle_numbers(cursor): rows = cursor.execute("SELECT number FROM puzzle ORDER BY number").fetchall() return [row[0] for row in rows] def get_puzzle_categories(cursor, puzzle_number): row = cursor.execute("SELECT data FROM puzzle WHERE number = ?", [puzzle_number]).fetchone() if not row: return None try: payload = json.loads(row[0]) categories = payload.get("categories") if isinstance(categories, list) and len(categories) == 4: return categories except (json.JSONDecodeError, TypeError): return None return None def render_puzzle(puzzle_number=None): connection = sqlite3.connect(config.db_file) cursor = connection.cursor() puzzle_numbers = get_puzzle_numbers(cursor) if puzzle_number is None and puzzle_numbers: puzzle_number = puzzle_numbers[-1] if puzzle_number is not None: categories = get_puzzle_categories(cursor, puzzle_number) if categories is None and puzzle_number in puzzle_numbers: categories = DEFAULT_CATEGORIES elif categories is None: connection.close() abort(404) else: categories = DEFAULT_CATEGORIES connection.close() return render_template( 'index.html', categories=categories, puzzle_numbers=puzzle_numbers, current_puzzle=puzzle_number, ) @app.route('/') def index(): return render_puzzle() @app.route('/') def puzzle_by_number(puzzle_number): return render_puzzle(puzzle_number) @app.route('/new', methods=['GET', 'POST']) def new_puzzle(): if request.method == 'GET': return render_template('new.html', creation_date=datetime.date.today().isoformat()) author = request.form.get("author", "").strip() creation_date = request.form.get("creation_date", "").strip() or datetime.date.today().isoformat() categories = [] for i in range(1, 5): name = request.form.get(f"category_{i}_name", "").strip() words = [] for j in range(1, 5): word = request.form.get(f"category_{i}_word_{j}", "").strip() if word: words.append(word) categories.append({"name": name, "items": words, "level": i}) if not author: return render_template( 'new.html', error="Author is required.", creation_date=creation_date, form=request.form ), 400 if any(not cat["name"] or len(cat["items"]) != 4 for cat in categories): return render_template( 'new.html', error="Each category needs a name and exactly 4 words.", creation_date=creation_date, form=request.form ), 400 data = json.dumps({"categories": categories}, separators=(",", ":")) connection = sqlite3.connect(config.db_file) cursor = connection.cursor() row = cursor.execute("SELECT COALESCE(MAX(number), 0) + 1 FROM puzzle").fetchone() number = row[0] cursor.execute( """ INSERT INTO puzzle (number, author, creation_date, data) VALUES (?, ?, ?, ?) """, [number, author, creation_date, data] ) connection.commit() connection.close() return render_template( 'new.html', success=f"Saved puzzle #{number}.", creation_date=creation_date ) if __name__ == '__main__': subprocess.run(["python3", "make_db.py"], check=True) app.run(host='0.0.0.0', port=8000)