From 8eaac47ef6a57b1e4f07cbae1baf20522f309cec Mon Sep 17 00:00:00 2001 From: Stefan Koelle Date: Fri, 7 Aug 2026 12:39:21 +0200 Subject: [PATCH] mail test method --- src/api/main.py | 54 +++++++++++++++++++++++++++++++ src/api/templates/admin.html | 61 ++++++++++++++++++++++++++++++++++++ src/mailer.py | 13 +++++--- 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/api/main.py b/src/api/main.py index 8118ccb..c16674f 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -23,6 +23,7 @@ import db from api.auth import get_current_user, resolve_account_for_user from api.schemas import ContactListResponse, ContactOut, SyncRunOut from config import Config +from mailer import build_message, fetch_birthdays_for_date, send_message logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger("api") @@ -283,6 +284,59 @@ def admin_toggle( return RedirectResponse(url="/admin", status_code=303) +@app.post("/admin/test-send") +def admin_test_send( + request: Request, + current_user: str = Depends(get_current_user), +): + _, is_admin, _ = _resolve_effective_account(request, current_user) + if not is_admin: + return RedirectResponse(url="/", status_code=303) + + target = date(2026, 8, 6) + try: + Config.validate_mailer() + except RuntimeError as exc: + return templates.TemplateResponse( + "admin.html", + { + "request": request, + "current_user": current_user, + "show_all": request.session.get("show_all", False), + "error": str(exc), + }, + status_code=400, + ) + + with db.get_connection() as conn: + birthdays = fetch_birthdays_for_date(conn, target) + + msg = build_message(birthdays, target_date=target) + try: + send_message(msg) + except Exception as exc: + return templates.TemplateResponse( + "admin.html", + { + "request": request, + "current_user": current_user, + "show_all": request.session.get("show_all", False), + "error": f"Versand fehlgeschlagen: {exc}", + }, + status_code=500, + ) + + return templates.TemplateResponse( + "admin.html", + { + "request": request, + "current_user": current_user, + "show_all": request.session.get("show_all", False), + "success": f"Test-Mail für {target.strftime('%d.%m.%Y')} gesendet ({len(birthdays)} Kontakte).", + }, + ) + + @app.get("/search/special", response_class=HTMLResponse) def web_search_special( request: Request, diff --git a/src/api/templates/admin.html b/src/api/templates/admin.html index 14aa1f8..a687b3c 100644 --- a/src/api/templates/admin.html +++ b/src/api/templates/admin.html @@ -143,6 +143,50 @@ background: #f8f9fa; color: #666; } + + .btn { + display: inline-block; + padding: 0.75rem 1.5rem; + background: #0066cc; + color: #fff; + border: none; + border-radius: 6px; + font-size: 0.875rem; + cursor: pointer; + text-decoration: none; + } + + .btn:hover { + background: #0055aa; + } + + .btn:disabled { + background: #ccc; + cursor: not-allowed; + } + + .flash { + margin-top: 1rem; + padding: 0.75rem; + border-radius: 6px; + font-size: 0.875rem; + } + + .flash-success { + background: #d4edda; + color: #155724; + } + + .flash-error { + background: #f8d7da; + color: #721c24; + } + + .test-info { + font-size: 0.8125rem; + color: #888; + margin-top: 0.75rem; + } @@ -172,6 +216,23 @@ {% endif %} + + {% if success %} +
{{ success }}
+ {% endif %} + {% if error %} +
{{ error }}
+ {% endif %} + +
+
E-Mail-Test
+
+ +
+
+ Sendet eine Test-Geburtstagsmail an die konfigurierte Adresse (MAIL_TO) mit allen Kontakten, die am 6. August Geburtstag haben. Das Layout entspricht der täglichen Geburtstagsmail. +
+
diff --git a/src/mailer.py b/src/mailer.py index c3161dc..55f1bed 100644 --- a/src/mailer.py +++ b/src/mailer.py @@ -18,8 +18,7 @@ logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] logger = logging.getLogger("mailer") -def fetch_todays_birthdays(conn) -> list[dict]: - today = date.today() +def fetch_birthdays_for_date(conn, target_date: date) -> list[dict]: with conn.cursor() as cur: cur.execute( """SELECT id, account, full_name, birthday, organization, photo_url @@ -28,11 +27,15 @@ def fetch_todays_birthdays(conn) -> list[dict]: AND MONTH(birthday) = %s AND DAY(birthday) = %s ORDER BY full_name""", - (today.month, today.day), + (target_date.month, target_date.day), ) return cur.fetchall() +def fetch_todays_birthdays(conn) -> list[dict]: + return fetch_birthdays_for_date(conn, date.today()) + + def already_sent_today(conn) -> bool: today = date.today() with conn.cursor() as cur: @@ -50,8 +53,8 @@ def log_sent(conn, count: int): conn.commit() -def build_message(birthdays: list[dict]) -> EmailMessage: - today = date.today() +def build_message(birthdays: list[dict], target_date: date | None = None) -> EmailMessage: + today = target_date or date.today() msg = EmailMessage() msg["From"] = Config.MAIL_FROM msg["To"] = Config.MAIL_TO