"""Business logic for contact submissions.""" from __future__ import annotations import logging import smtplib from dataclasses import dataclass from datetime import datetime, timezone from email.message import EmailMessage from typing import Any, Dict, Tuple, cast from ..database import save_contact from ..metrics import record_submission from ..utils import is_valid_email from .email_settings import load_effective_smtp_settings @dataclass class ContactSubmission: name: str email: str company: str | None message: str timeline: str | None created_at: str = datetime.now(timezone.utc).isoformat() def validate_submission(raw: Dict[str, Any]) -> Tuple[ContactSubmission | None, Dict[str, str]]: """Validate the incoming payload and return a submission object.""" name = (raw.get("name") or "").strip() email = (raw.get("email") or "").strip() message = (raw.get("message") or "").strip() consent = raw.get("consent") company = (raw.get("company") or "").strip() errors: Dict[str, str] = {} if not name: errors["name"] = "Name is required." elif len(name) > 200: errors["name"] = "Name is too long (max 200 chars)." if not is_valid_email(email): errors["email"] = "Valid email is required." if not message: errors["message"] = "Message is required." elif len(message) > 5000: errors["message"] = "Message is too long (max 5000 chars)." if not consent: errors["consent"] = "Consent is required." if company and len(company) > 200: errors["company"] = "Organisation name is too long (max 200 chars)." if errors: return None, errors submission = ContactSubmission( name=name, email=email, company=company or None, message=message, timeline=(raw.get("timeline") or "").strip() or None, ) return submission, {} def persist_submission(submission: ContactSubmission) -> int: """Persist the submission and update metrics.""" record_id = save_contact(submission) record_submission() return record_id def send_notification(submission: ContactSubmission) -> bool: """Send an email notification for the submission if SMTP is configured.""" smtp_config = load_effective_smtp_settings() if not smtp_config.get("notify_contact_form"): logging.info("Contact notifications disabled; skipping email dispatch") return False if not smtp_config.get("host") or not smtp_config.get("recipients"): logging.info("SMTP not configured; skipping email notification") return False sender = smtp_config.get("sender") or "no-reply@example.com" recipients = smtp_config.get("recipients", []) host = cast(str, smtp_config.get("host")) port = int(smtp_config.get("port") or 0) username = smtp_config.get("username") or None password = smtp_config.get("password") or "" msg = EmailMessage() msg["Subject"] = f"Neue Kontaktanfrage von {submission.name}" msg["From"] = sender msg["To"] = ", ".join(recipients) msg.set_content( "\n".join( [ f"Name: {submission.name}", f"E-Mail: {submission.email}", f"Organisation: {submission.company or '—'}", f"Zeithorizont: {submission.timeline or '—'}", "", "Nachricht:", submission.message, "", f"Eingang: {submission.created_at}", ] ) ) try: with smtplib.SMTP(host, port, timeout=15) as server: if smtp_config.get("use_tls"): server.starttls() if username: server.login(username, password) server.send_message(msg) logging.info("Notification email dispatched to %s", recipients) return True except Exception as exc: # pragma: no cover - SMTP failures are logged only logging.error("Failed to send notification email: %s", exc) return False