refactor: form-core wiring, JWT sessions, slowapi, packaging, frontend rewrite, test declutter

This commit is contained in:
Jonas Miederer 2026-09-28 18:03:07 +02:00
parent 06ab25fb47
commit 03af41e837
74 changed files with 6089 additions and 5183 deletions

View file

@ -9,8 +9,10 @@ fonts, no external images (many clients block them / track opens).
from __future__ import annotations
import logging
import smtplib
from email.message import EmailMessage
from pathlib import Path
from jinja2 import Template
from app.settings import settings
@ -22,67 +24,62 @@ BLUE_LIGHT = "#eef4fb"
INK = "#162f4c"
MUTED = "#5b6b7c"
_TEMPLATE_PATH = Path(__file__).resolve().parent / "templates" / "magic_link.html"
_template_cache: Template | None = None
def _template() -> Template:
global _template_cache
if _template_cache is None:
_template_cache = Template(_TEMPLATE_PATH.read_text(encoding="utf-8"))
return _template_cache
def magic_link(token: str) -> str:
return f"{settings.frontend_url.rstrip('/')}/login?token={token}"
def _page(title: str, intro: str, button_label: str, button_url: str | None, note: str) -> str:
"""Branded HTML mail body (table layout, inline styles, no images)."""
button = ""
if button_url:
button = (
'<table role="presentation" cellpadding="0" cellspacing="0" '
'style="margin:24px auto 8px auto;"><tr>'
f'<td bgcolor="{BLUE}" style="border-radius:8px;">'
f'<a href="{button_url}" '
'style="display:inline-block;padding:12px 28px;font-family:-apple-system,'
"Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:15px;"
"font-weight:600;color:#ffffff;text-decoration:none;"
f'border-radius:8px;">{button_label}</a></td></tr></table>'
)
return f"""<!DOCTYPE html>
<html lang="de">
<body style="margin:0;padding:0;background-color:{BLUE_LIGHT};">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0"
style="background-color:{BLUE_LIGHT};padding:24px 12px;">
<tr><td align="center">
<table role="presentation" width="520" cellpadding="0" cellspacing="0"
style="max-width:520px;width:100%;font-family:Georgia,'Times New Roman',serif;
border-radius:12px;overflow:hidden;border:1px solid #d9e6f5;">
<tr>
<td bgcolor="{BLUE}" style="padding:20px 28px;">
<span style="color:#ffffff;font-size:18px;font-weight:700;
letter-spacing:0.02em;">Kirche Einfach</span>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" style="padding:28px 28px 24px 28px;">
<h1 style="margin:0 0 12px 0;font-size:22px;line-height:1.3;
color:{INK};">{title}</h1>
<p style="margin:0;font-family:-apple-system,Segoe UI,Roboto,
Helvetica,Arial,sans-serif;font-size:15px;line-height:1.6;
color:{MUTED};">{intro}</p>
{button}
</td>
</tr>
</table>
<p style="font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
font-size:12px;color:{MUTED};margin:16px 0 0 0;max-width:520px;">
{note}
</p>
</td></tr>
</table>
</body>
</html>"""
"""Branded HTML mail body (jinja template, byte-identical to the old f-string)."""
return _template().render(
title=title,
intro=intro,
button_label=button_label,
button_url=button_url,
note=note,
blue=BLUE,
blue_light=BLUE_LIGHT,
ink=INK,
muted=MUTED,
)
def _send(message: EmailMessage) -> None:
with smtplib.SMTP(settings.smtp_host, settings.smtp_port, timeout=10) as smtp:
smtp.starttls()
if settings.smtp_username:
smtp.login(settings.smtp_username, settings.smtp_password)
smtp.send_message(message)
"""Deliver via fastapi-mail (same never-raise contract at call sites)."""
import asyncio
from fastapi_mail import ConnectionConfig, FastMail, MessageSchema, MessageType
config = ConnectionConfig(
MAIL_USERNAME=settings.smtp_username,
MAIL_PASSWORD=settings.smtp_password,
MAIL_FROM=settings.mail_from or settings.contact_to or message["To"],
MAIL_PORT=settings.smtp_port,
MAIL_SERVER=settings.smtp_host,
MAIL_STARTTLS=True,
MAIL_SSL_TLS=False,
USE_CREDENTIALS=bool(settings.smtp_username),
)
html = message.get_body(preferencelist=("html",))
plain = message.get_body(preferencelist=("plain",))
outgoing = MessageSchema(
subject=str(message["Subject"]),
recipients=[str(message["To"])],
body=plain.get_content() if plain else "",
html=html.get_content() if html else "",
subtype=MessageType.html,
)
asyncio.run(FastMail(config).send_message(outgoing))
def _branded_mail(
@ -142,7 +139,7 @@ def send_magic_link(to_email: str, link: str) -> bool:
)
try:
_send(message)
except (OSError, smtplib.SMTPException):
except Exception: # noqa: BLE001 -- never-raise contract: delivery must not 500
logger.warning("magic-link delivery failed for %s", to_email)
return False
logger.info("magic link mailed to %s", to_email)
@ -167,7 +164,7 @@ def send_access_notification(request_id: str, name: str, email: str) -> bool:
)
try:
_send(message)
except (OSError, smtplib.SMTPException):
except Exception: # noqa: BLE001 -- best-effort notification, never raises
logger.warning("access-request notification failed for %s", request_id)
return False
return True