127 lines
3.6 KiB
Python
Executable File
127 lines
3.6 KiB
Python
Executable File
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('/<int:puzzle_number>')
|
|
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)
|