Fix bugs, add log rotation, and optimize hot paths

- Fix AttributeError crash on empty request body in md5, hasher, textdiff,
  jwtdecoder, timestamp, passwordgen (get_json without silent=True / or {})
- Fix memory exhaustion in ipcalc: replace list(network.hosts()) with direct
  arithmetic — safe for /8 and larger networks
- Fix O(1M) loop in cronexplainer.get_next_runs: rewrite to skip by
  month/day/hour instead of iterating every minute
- Fix connection leak in notes.ensure_table: add try/finally around conn.close
- Fix admin._ensure_tables / notes._ensure_table running DDL on every request:
  guard with module-level flags (_tables_initialized, _table_ready)
- Fix update_website returning 200 when no row matched; delete_website returning
  success when nothing was deleted; add rowcount checks for both
- Add role validation in admin create_user / update_user (_VALID_ROLES guard)
- Add delimiter length guard in csvviewer (csv.reader requires single char)
- Fix loremipsum: wrap int(count) in try/except ValueError → 400 response
- Fix auth/token: use auth_header[7:] instead of fragile .replace()
- Fix app.py: remove duplicate import sys; cache DB liveness check with 30s TTL
  to avoid a new TCP connection on every frontend page load; move api/setup
  path guard before DB check
- Replace FileHandler with RotatingFileHandler (5 MB / 3 backups) in logger;
  fix relative log paths to absolute paths anchored to __file__
- Wrap all DB connections in try/finally conn.close() throughout admin and notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nirodan
2026-05-06 10:06:29 +02:00
parent 31494c9dab
commit 98bb34f094
15 changed files with 363 additions and 196 deletions
+22 -11
View File
@@ -1,10 +1,11 @@
import os
import sys
import time as _time
if __name__ != '__main__':
import sys
sys.path.append(os.path.dirname(__file__))
from flask import Flask, send_from_directory, redirect
from flask import Flask, send_from_directory, redirect, abort
from util.logger import logger
from util.db_config import is_configured, load_config, test_connection
from util.setup_routes import setup_blueprint
@@ -37,7 +38,6 @@ from admin import admin_bp
app = Flask(__name__, template_folder="templates")
limiter.init_app(app)
# Blueprints registrieren
app.register_blueprint(setup_blueprint)
app.register_blueprint(auth_bp)
app.register_blueprint(md5_blueprint)
@@ -62,24 +62,35 @@ app.register_blueprint(csv_blueprint)
app.register_blueprint(notes_blueprint)
app.register_blueprint(admin_bp)
# 🌐 React-Frontend ausliefern
# Cache DB liveness check so we don't open a new TCP connection on every page load.
_db_check = {"ok": False, "ts": 0.0}
_DB_CHECK_TTL = 30.0 # seconds
def _is_db_ready():
now = _time.monotonic()
if now - _db_check["ts"] > _DB_CHECK_TTL:
_db_check["ok"] = is_configured() and bool(test_connection(load_config()))
_db_check["ts"] = now
return _db_check["ok"]
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def serve_frontend(path):
if not is_configured() or not test_connection(load_config()):
return redirect('/setup')
if path.startswith('setup') or path.startswith('api'):
from flask import abort
# Unmatched API / setup paths get a clean 404 before any DB work.
if path.startswith('api') or path.startswith('setup'):
abort(404)
if not _is_db_ready():
return redirect('/setup')
dist_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'frontend', 'dist'))
file_path = os.path.join(dist_dir, path)
if path and os.path.exists(file_path):
return send_from_directory(dist_dir, path)
else:
return send_from_directory(dist_dir, 'index.html')
return send_from_directory(dist_dir, 'index.html')
if __name__ == '__main__':