diff --git a/fragify.py b/fragify.py index 32e8a9f..6210488 100644 --- a/fragify.py +++ b/fragify.py @@ -11,6 +11,8 @@ import os import sys from jinja2 import Environment, FileSystemLoader +SITE_BASE_URL = os.environ.get('FRAGIFY_BASE_URL', 'http://localhost:8000') + class BaseTemplateResource: """Base class for resources that need template rendering""" @@ -55,16 +57,6 @@ class BaseTemplateResource: # Fallback to current directory return dev_template_dir -class StaticResource: - def __init__(self, directory: str): - self._directory = directory - self._static = falcon.asgi.StaticRoute(directory) - - def on_get(self, req, resp, path): - # Serve static files via falcon's static route helper - static_app = falcon.asgi.StaticRoute(self._directory) - return static_app(req, resp) - class FragifyApp(BaseTemplateResource): def __init__(self): self.fragdenstaat_api = "https://fragdenstaat.de/api/v1" @@ -77,7 +69,11 @@ class FragifyApp(BaseTemplateResource): """Serve the main page""" template = self.jinja_env.get_template('index.html') resp.content_type = 'text/html; charset=utf-8' - resp.text = template.render() + resp.text = template.render( + meta_title='Fragify – Anfragelinks für FragDenStaat', + meta_description='Erstelle vorausgefüllte Anfragelinks für FragDenStaat.de, suche Behörden, füge Betreff und Text hinzu und teile den Link.', + canonical_url=f"{SITE_BASE_URL}/" + ) def on_post(self, req, resp): """Handle form submission and generate link""" @@ -124,7 +120,12 @@ class ImpressumResource(BaseTemplateResource): """Serve the Impressum page""" template = self.jinja_env.get_template('impressum.html') resp.content_type = 'text/html; charset=utf-8' - resp.text = template.render() + resp.text = template.render( + meta_title='Impressum – Fragify', + meta_description='Impressum für Fragify.', + canonical_url=f"{SITE_BASE_URL}/impressum", + noindex=True + ) class DatenschutzResource(BaseTemplateResource): def __init__(self): @@ -135,7 +136,12 @@ class DatenschutzResource(BaseTemplateResource): """Serve the Datenschutz page""" template = self.jinja_env.get_template('datenschutz.html') resp.content_type = 'text/html; charset=utf-8' - resp.text = template.render() + resp.text = template.render( + meta_title='Datenschutz – Fragify', + meta_description='Datenschutzerklärung für Fragify. Keine Cookies, es werden nur Anfragen an die FragDenStaat-API gestellt.', + canonical_url=f"{SITE_BASE_URL}/datenschutz", + noindex=True + ) class PublicBodiesResource: def __init__(self): @@ -175,6 +181,25 @@ class PublicBodiesResource: 'next': None }) +class RobotsResource: + def on_get(self, req, resp): + resp.content_type = 'text/plain; charset=utf-8' + resp.text = f"""User-agent: * +Allow: / +Sitemap: {SITE_BASE_URL}/sitemap.xml +""" + +class SitemapResource: + def on_get(self, req, resp): + resp.content_type = 'application/xml; charset=utf-8' + resp.text = f""" + + {SITE_BASE_URL}/ + {SITE_BASE_URL}/impressum + {SITE_BASE_URL}/datenschutz + +""" + # Create Falcon application app = falcon.App() @@ -200,11 +225,15 @@ fragify = FragifyApp() impressum = ImpressumResource() datenschutz = DatenschutzResource() publicbodies = PublicBodiesResource() +robots = RobotsResource() +sitemap = SitemapResource() app.add_route('/', fragify) app.add_route('/impressum', impressum) app.add_route('/datenschutz', datenschutz) app.add_route('/api/publicbodies', publicbodies) +app.add_route('/robots.txt', robots) +app.add_route('/sitemap.xml', sitemap) # Static file route if STATIC_DIR and os.path.isdir(STATIC_DIR): diff --git a/module.nix b/module.nix index 7bdcf33..b0250e7 100644 --- a/module.nix +++ b/module.nix @@ -14,47 +14,48 @@ in options = { services.fragify = { - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Enable fragify web application. - ''; - }; + enable = lib.mkEnableOption "Fragify web app"; }; }; config = lib.mkIf cfg.enable { - # uWSGI application definition for Fragify - services.uwsgi.enable = true; - services.uwsgi.user = "fragify"; - services.uwsgi.group = "fragify"; - services.uwsgi.plugins = [ "python3" ]; - services.uwsgi.instance."fragify" = { - type = "normal"; - chdir = "/"; - # Load WSGI by file path from the packaged share dir - wsgi-file = "${pkgs.fragify}/share/fragify/fragify_wsgi.py"; - module = "fragify:app"; - pythonPackages = p: with p; [ falcon requests jinja2 ]; - env = { - FRAGIFY_TEMPLATES_DIR = "${pkgs.fragify}/share/fragify/templates"; - FRAGIFY_STATIC_DIR = "${pkgs.fragify}/share/fragify/assets"; + services.uwsgi = { + enable = true; + user = "fragify"; + group = "fragify"; + plugins = [ "python3" ]; + instances.fragify = { + # Align with upstream module: put uwsgi options under settings + settings = { + "chdir" = "/"; + "wsgi-file" = "${pkgs.fragify}/share/fragify/fragify_wsgi.py"; + module = "fragify:app"; + # Socket + "socket" = "unix:${config.services.uwsgi.runDir}/fragify.sock"; + "chmod-socket" = "660"; + umask = "0077"; + vacuum = true; + master = true; + processes = 2; + threads = 2; + "harakiri" = 60; + "buffer-size" = 65535; + "need-app" = true; + "no-orphans" = true; + # Serve static files directly via uWSGI (optional) + # Map /static to packaged assets directory (if present) + "static-map" = "/static=${pkgs.fragify}/share/fragify/assets"; + }; + # Environment for the WSGI app + env = { + FRAGIFY_TEMPLATES_DIR = "${pkgs.fragify}/share/fragify/templates"; + FRAGIFY_STATIC_DIR = "${pkgs.fragify}/share/fragify/assets"; + }; + # Python packages for uWSGI + pythonPackages = p: with p; [ falcon requests jinja2 ]; }; - socket = "unix:${config.services.uwsgi.runDir}/fragify.sock"; - chmod-socket = "660"; - umask = "0077"; - vacuum = true; - master = true; - processes = 2; - threads = 2; - harakiri = 60; - buffer-size = 65535; - # Security hardening - need-app = true; - no-orphans = true; }; # Ensure fragify user and group exist diff --git a/templates/base.html b/templates/base.html index 6c22c18..9121a9a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -23,6 +23,7 @@ + {% if noindex %}{% endif %}