add SEO, update module

This commit is contained in:
Jonas Heinrich 2025-08-19 10:14:39 +02:00
parent ef20108403
commit 892fb25d06
3 changed files with 78 additions and 47 deletions

View file

@ -11,6 +11,8 @@ import os
import sys import sys
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
SITE_BASE_URL = os.environ.get('FRAGIFY_BASE_URL', 'http://localhost:8000')
class BaseTemplateResource: class BaseTemplateResource:
"""Base class for resources that need template rendering""" """Base class for resources that need template rendering"""
@ -55,16 +57,6 @@ class BaseTemplateResource:
# Fallback to current directory # Fallback to current directory
return dev_template_dir 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): class FragifyApp(BaseTemplateResource):
def __init__(self): def __init__(self):
self.fragdenstaat_api = "https://fragdenstaat.de/api/v1" self.fragdenstaat_api = "https://fragdenstaat.de/api/v1"
@ -77,7 +69,11 @@ class FragifyApp(BaseTemplateResource):
"""Serve the main page""" """Serve the main page"""
template = self.jinja_env.get_template('index.html') template = self.jinja_env.get_template('index.html')
resp.content_type = 'text/html; charset=utf-8' 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): def on_post(self, req, resp):
"""Handle form submission and generate link""" """Handle form submission and generate link"""
@ -124,7 +120,12 @@ class ImpressumResource(BaseTemplateResource):
"""Serve the Impressum page""" """Serve the Impressum page"""
template = self.jinja_env.get_template('impressum.html') template = self.jinja_env.get_template('impressum.html')
resp.content_type = 'text/html; charset=utf-8' 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): class DatenschutzResource(BaseTemplateResource):
def __init__(self): def __init__(self):
@ -135,7 +136,12 @@ class DatenschutzResource(BaseTemplateResource):
"""Serve the Datenschutz page""" """Serve the Datenschutz page"""
template = self.jinja_env.get_template('datenschutz.html') template = self.jinja_env.get_template('datenschutz.html')
resp.content_type = 'text/html; charset=utf-8' 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: class PublicBodiesResource:
def __init__(self): def __init__(self):
@ -175,6 +181,25 @@ class PublicBodiesResource:
'next': None '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"""<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>{SITE_BASE_URL}/</loc></url>
<url><loc>{SITE_BASE_URL}/impressum</loc></url>
<url><loc>{SITE_BASE_URL}/datenschutz</loc></url>
</urlset>
"""
# Create Falcon application # Create Falcon application
app = falcon.App() app = falcon.App()
@ -200,11 +225,15 @@ fragify = FragifyApp()
impressum = ImpressumResource() impressum = ImpressumResource()
datenschutz = DatenschutzResource() datenschutz = DatenschutzResource()
publicbodies = PublicBodiesResource() publicbodies = PublicBodiesResource()
robots = RobotsResource()
sitemap = SitemapResource()
app.add_route('/', fragify) app.add_route('/', fragify)
app.add_route('/impressum', impressum) app.add_route('/impressum', impressum)
app.add_route('/datenschutz', datenschutz) app.add_route('/datenschutz', datenschutz)
app.add_route('/api/publicbodies', publicbodies) app.add_route('/api/publicbodies', publicbodies)
app.add_route('/robots.txt', robots)
app.add_route('/sitemap.xml', sitemap)
# Static file route # Static file route
if STATIC_DIR and os.path.isdir(STATIC_DIR): if STATIC_DIR and os.path.isdir(STATIC_DIR):

View file

@ -14,47 +14,48 @@ in
options = { options = {
services.fragify = { services.fragify = {
enable = lib.mkOption { enable = lib.mkEnableOption "Fragify web app";
type = lib.types.bool;
default = false;
description = ''
Enable fragify web application.
'';
};
}; };
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
# uWSGI application definition for Fragify services.uwsgi = {
services.uwsgi.enable = true; enable = true;
services.uwsgi.user = "fragify"; user = "fragify";
services.uwsgi.group = "fragify"; group = "fragify";
services.uwsgi.plugins = [ "python3" ]; plugins = [ "python3" ];
services.uwsgi.instance."fragify" = { instances.fragify = {
type = "normal"; # Align with upstream module: put uwsgi options under settings
chdir = "/"; settings = {
# Load WSGI by file path from the packaged share dir "chdir" = "/";
wsgi-file = "${pkgs.fragify}/share/fragify/fragify_wsgi.py"; "wsgi-file" = "${pkgs.fragify}/share/fragify/fragify_wsgi.py";
module = "fragify:app"; module = "fragify:app";
pythonPackages = p: with p; [ falcon requests jinja2 ]; # Socket
env = { "socket" = "unix:${config.services.uwsgi.runDir}/fragify.sock";
FRAGIFY_TEMPLATES_DIR = "${pkgs.fragify}/share/fragify/templates"; "chmod-socket" = "660";
FRAGIFY_STATIC_DIR = "${pkgs.fragify}/share/fragify/assets"; 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 # Ensure fragify user and group exist

View file

@ -23,6 +23,7 @@
<meta name="twitter:title" content="{{ meta_title | default('Fragify') }}"> <meta name="twitter:title" content="{{ meta_title | default('Fragify') }}">
<meta name="twitter:description" content="{{ meta_description | default('Erstelle vorausgefüllte FragDenStaat.de-Anfragelinks und teile sie mit Freund:innen.') }}"> <meta name="twitter:description" content="{{ meta_description | default('Erstelle vorausgefüllte FragDenStaat.de-Anfragelinks und teile sie mit Freund:innen.') }}">
{% if noindex %}<meta name="robots" content="noindex,follow">{% endif %}
<meta name="theme-color" content="#667eea"> <meta name="theme-color" content="#667eea">
<link rel="icon" href="/static/favicon.ico"> <link rel="icon" href="/static/favicon.ico">