slop game oder so

This commit is contained in:
2026-09-24 17:57:54 +02:00
commit 4c997eee21
166 changed files with 14976 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
"""Glory Defense – Flask-App-Factory.
Die App ist bewusst monolithisch und zustandslos (Zustand nur in SQLite), damit sie
unter Phusion Passenger mit mehreren Prozessen und ohne Hintergrunddienste läuft.
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from flask import Flask, Response, jsonify, request
from werkzeug.exceptions import HTTPException
from werkzeug.middleware.proxy_fix import ProxyFix
from . import api, models, routes
from .config import INSTANCE_DIR, load_config
#: Content-Security-Policy: Skripte nur vom eigenen Server (Phaser liegt lokal).
#: ``blob:``/``data:`` braucht Phaser, weil es geladene Bilder, SVGs und Audio als
#: Blob-URLs dekodiert und eingebaute Standardtexturen als Data-URLs mitbringt.
CONTENT_SECURITY_POLICY = "; ".join(
[
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"media-src 'self' data: blob:",
"font-src 'self' data:",
"connect-src 'self' data: blob:",
"worker-src 'self' blob:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'self'",
]
)
def _apply_security_headers(response: Response) -> Response:
response.headers.setdefault("Content-Security-Policy", CONTENT_SECURITY_POLICY)
response.headers.setdefault("X-Content-Type-Options", "nosniff")
response.headers.setdefault("Referrer-Policy", "same-origin")
response.headers.setdefault("X-Frame-Options", "SAMEORIGIN")
return response
def _handle_http_error(exc: HTTPException) -> Response | HTTPException:
"""API-Pfade bekommen Fehler als JSON, damit das Frontend sie einheitlich auswerten kann."""
if request.path.startswith("/api/"):
body: dict[str, Any] = {
"error": (exc.name or "error").lower().replace(" ", "_"),
"message": exc.description or "",
}
response = jsonify(body)
response.status_code = exc.code or 500
return response
return exc
def create_app(test_config: Mapping[str, Any] | None = None) -> Flask:
"""Erzeugt und konfiguriert die Flask-App.
Args:
test_config: Optionale Konfigurationswerte mit Vorrang vor den
Umgebungsvariablen (vor allem für Tests).
Returns:
Die fertig konfigurierte Flask-Anwendung.
"""
app = Flask(__name__, instance_path=str(INSTANCE_DIR))
app.config.from_mapping(load_config(test_config))
# Antworten in der Reihenfolge, in der der Code die Felder aufbaut (lesbarer).
app.json.sort_keys = False # type: ignore[attr-defined]
if app.config["TRUST_PROXY"]:
# Nur hinter einem vertrauenswürdigen Proxy aktivieren, sonst könnten Clients
# ihre IP (und damit das Rate-Limit) per Header fälschen. Übernommen werden nur
# Client-IP und Protokoll: Host und Pfadpräfix aus Headern landeten sonst in den
# erzeugten URLs (z. B. X-Forwarded-Prefix: //fremde.domain).
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1) # type: ignore[method-assign]
models.init_app(app)
app.register_blueprint(routes.bp)
app.register_blueprint(api.bp)
app.after_request(_apply_security_headers)
app.register_error_handler(HTTPException, _handle_http_error)
return app