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 %}