Aggiunge invio prenotazioni con Mailjet
This commit is contained in:
@@ -3,11 +3,25 @@ APP_ENV=development
|
|||||||
APP_HOST=127.0.0.1
|
APP_HOST=127.0.0.1
|
||||||
APP_PORT=8000
|
APP_PORT=8000
|
||||||
DATABASE_URL=sqlite:///C:/devel/clinica_veterinaria_formiginese/data/clinica.db
|
DATABASE_URL=sqlite:///C:/devel/clinica_veterinaria_formiginese/data/clinica.db
|
||||||
|
|
||||||
|
MAIL_PROVIDER=mailjet
|
||||||
|
MAILJET_API_KEY=
|
||||||
|
MAILJET_API_SECRET=
|
||||||
|
MAILJET_API_URL=https://api.mailjet.com/v3.1/send
|
||||||
|
MAILJET_SANDBOX_MODE=true
|
||||||
|
|
||||||
SMTP_HOST=smtp.gmail.com
|
SMTP_HOST=smtp.gmail.com
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_USERNAME=
|
SMTP_USERNAME=
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
SMTP_USE_TLS=true
|
SMTP_USE_TLS=true
|
||||||
BOOKING_EMAIL_FROM=
|
BOOKING_EMAIL_FROM=
|
||||||
|
BOOKING_EMAIL_FROM_NAME=Clinica Veterinaria Formiginese
|
||||||
BOOKING_EMAIL_TO=
|
BOOKING_EMAIL_TO=
|
||||||
BOOKING_EMAIL_SUBJECT_PREFIX=[Clinica Veterinaria Formiginese]
|
BOOKING_EMAIL_SUBJECT_PREFIX=[Clinica Veterinaria Formiginese]
|
||||||
|
BOOKING_FORM_ENABLED=false
|
||||||
|
BOOKING_AUDIT_LOG_PATH=
|
||||||
|
BOOKING_RATE_LIMIT_WINDOW_SECONDS=900
|
||||||
|
BOOKING_RATE_LIMIT_MAX_REQUESTS=5
|
||||||
|
BOOKING_CAPTCHA_TTL_SECONDS=600
|
||||||
|
BOOKING_CAPTCHA_SECRET=
|
||||||
|
|||||||
@@ -27,11 +27,22 @@ class Settings:
|
|||||||
self.smtp_password = os.getenv("SMTP_PASSWORD", "")
|
self.smtp_password = os.getenv("SMTP_PASSWORD", "")
|
||||||
self.smtp_use_tls = os.getenv("SMTP_USE_TLS", "true").lower() in {"1", "true", "yes", "on"}
|
self.smtp_use_tls = os.getenv("SMTP_USE_TLS", "true").lower() in {"1", "true", "yes", "on"}
|
||||||
self.booking_email_from = os.getenv("BOOKING_EMAIL_FROM", self.smtp_username)
|
self.booking_email_from = os.getenv("BOOKING_EMAIL_FROM", self.smtp_username)
|
||||||
|
self.booking_email_from_name = os.getenv("BOOKING_EMAIL_FROM_NAME", "Clinica Veterinaria Formiginese")
|
||||||
self.booking_email_to = os.getenv("BOOKING_EMAIL_TO", "")
|
self.booking_email_to = os.getenv("BOOKING_EMAIL_TO", "")
|
||||||
self.booking_email_subject_prefix = os.getenv(
|
self.booking_email_subject_prefix = os.getenv(
|
||||||
"BOOKING_EMAIL_SUBJECT_PREFIX",
|
"BOOKING_EMAIL_SUBJECT_PREFIX",
|
||||||
"[Clinica Veterinaria Formiginese]",
|
"[Clinica Veterinaria Formiginese]",
|
||||||
)
|
)
|
||||||
|
self.mail_provider = os.getenv("MAIL_PROVIDER", "mailjet").lower()
|
||||||
|
self.mailjet_api_key = os.getenv("MAILJET_API_KEY", "")
|
||||||
|
self.mailjet_api_secret = os.getenv("MAILJET_API_SECRET", "")
|
||||||
|
self.mailjet_api_url = os.getenv("MAILJET_API_URL", "https://api.mailjet.com/v3.1/send")
|
||||||
|
self.mailjet_sandbox_mode = os.getenv("MAILJET_SANDBOX_MODE", "false").lower() in {
|
||||||
|
"1",
|
||||||
|
"true",
|
||||||
|
"yes",
|
||||||
|
"on",
|
||||||
|
}
|
||||||
self.booking_form_enabled = os.getenv("BOOKING_FORM_ENABLED", "false").lower() in {
|
self.booking_form_enabled = os.getenv("BOOKING_FORM_ENABLED", "false").lower() in {
|
||||||
"1",
|
"1",
|
||||||
"true",
|
"true",
|
||||||
@@ -59,6 +70,17 @@ class Settings:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def booking_mail_enabled(self) -> bool:
|
def booking_mail_enabled(self) -> bool:
|
||||||
|
if self.mail_provider == "mailjet":
|
||||||
|
return all(
|
||||||
|
[
|
||||||
|
self.mailjet_api_key,
|
||||||
|
self.mailjet_api_secret,
|
||||||
|
self.booking_email_from,
|
||||||
|
self.booking_email_to,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.mail_provider == "smtp":
|
||||||
return all(
|
return all(
|
||||||
[
|
[
|
||||||
self.smtp_host,
|
self.smtp_host,
|
||||||
@@ -70,5 +92,7 @@ class Settings:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
|
from html import escape
|
||||||
|
import json
|
||||||
import smtplib
|
import smtplib
|
||||||
|
from urllib.error import HTTPError, URLError
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.schemas.booking import BookingRequestCreate
|
from app.schemas.booking import BookingRequestCreate
|
||||||
@@ -12,28 +17,42 @@ class MailConfigurationError(RuntimeError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MailDeliveryError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _build_booking_subject(payload: BookingRequestCreate) -> str:
|
def _build_booking_subject(payload: BookingRequestCreate) -> str:
|
||||||
return f"{settings.booking_email_subject_prefix} Nuova richiesta visita - {payload.name}"
|
return f"{settings.booking_email_subject_prefix} Nuova richiesta visita - {payload.name}"
|
||||||
|
|
||||||
|
|
||||||
def _build_booking_html(payload: BookingRequestCreate) -> str:
|
def _build_booking_html(payload: BookingRequestCreate) -> str:
|
||||||
submitted_at = datetime.now().strftime("%d/%m/%Y %H:%M")
|
submitted_at = datetime.now().strftime("%d/%m/%Y %H:%M")
|
||||||
|
name = escape(payload.name)
|
||||||
|
email = escape(payload.email)
|
||||||
|
phone = escape(payload.phone)
|
||||||
|
pet_name = escape(payload.pet_name or "-")
|
||||||
|
pet_type = escape(payload.pet_type)
|
||||||
|
doctor = escape(payload.doctor)
|
||||||
|
service = escape(payload.service)
|
||||||
|
date = escape(payload.date)
|
||||||
|
time = escape(payload.time or "Qualsiasi orario")
|
||||||
|
notes = escape(payload.notes or "-")
|
||||||
return f"""
|
return f"""
|
||||||
<html>
|
<html>
|
||||||
<body style="font-family: Arial, sans-serif; color: #1f2937; line-height: 1.6;">
|
<body style="font-family: Arial, sans-serif; color: #1f2937; line-height: 1.6;">
|
||||||
<h2 style="color: #1B4F72;">Nuova richiesta di prenotazione visita</h2>
|
<h2 style="color: #1B4F72;">Nuova richiesta di prenotazione visita</h2>
|
||||||
<p>È stata inviata una nuova richiesta di prenotazione non vincolante dal sito web.</p>
|
<p>È stata inviata una nuova richiesta di prenotazione non vincolante dal sito web.</p>
|
||||||
<table cellpadding="8" cellspacing="0" border="0" style="border-collapse: collapse;">
|
<table cellpadding="8" cellspacing="0" border="0" style="border-collapse: collapse;">
|
||||||
<tr><td><strong>Nome e cognome</strong></td><td>{payload.name}</td></tr>
|
<tr><td><strong>Nome e cognome</strong></td><td>{name}</td></tr>
|
||||||
<tr><td><strong>Email</strong></td><td>{payload.email}</td></tr>
|
<tr><td><strong>Email</strong></td><td>{email}</td></tr>
|
||||||
<tr><td><strong>Telefono</strong></td><td>{payload.phone}</td></tr>
|
<tr><td><strong>Telefono</strong></td><td>{phone}</td></tr>
|
||||||
<tr><td><strong>Nome animale</strong></td><td>{payload.pet_name or "-"}</td></tr>
|
<tr><td><strong>Nome animale</strong></td><td>{pet_name}</td></tr>
|
||||||
<tr><td><strong>Tipo animale</strong></td><td>{payload.pet_type}</td></tr>
|
<tr><td><strong>Tipo animale</strong></td><td>{pet_type}</td></tr>
|
||||||
<tr><td><strong>Medico richiesto</strong></td><td>{payload.doctor}</td></tr>
|
<tr><td><strong>Medico richiesto</strong></td><td>{doctor}</td></tr>
|
||||||
<tr><td><strong>Tipo di visita</strong></td><td>{payload.service}</td></tr>
|
<tr><td><strong>Tipo di visita</strong></td><td>{service}</td></tr>
|
||||||
<tr><td><strong>Periodo preferito</strong></td><td>{payload.date}</td></tr>
|
<tr><td><strong>Periodo preferito</strong></td><td>{date}</td></tr>
|
||||||
<tr><td><strong>Fascia oraria preferita</strong></td><td>{payload.time or "Qualsiasi orario"}</td></tr>
|
<tr><td><strong>Fascia oraria preferita</strong></td><td>{time}</td></tr>
|
||||||
<tr><td><strong>Note</strong></td><td>{payload.notes or "-"}</td></tr>
|
<tr><td><strong>Note</strong></td><td>{notes}</td></tr>
|
||||||
<tr><td><strong>Inviata il</strong></td><td>{submitted_at}</td></tr>
|
<tr><td><strong>Inviata il</strong></td><td>{submitted_at}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
<p style="margin-top: 20px;">
|
<p style="margin-top: 20px;">
|
||||||
@@ -65,6 +84,21 @@ def _build_booking_text(payload: BookingRequestCreate) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def send_booking_request_email(payload: BookingRequestCreate) -> None:
|
def send_booking_request_email(payload: BookingRequestCreate) -> None:
|
||||||
|
if not settings.booking_mail_enabled:
|
||||||
|
raise MailConfigurationError(f"Configurazione email incompleta per provider {settings.mail_provider}.")
|
||||||
|
|
||||||
|
if settings.mail_provider == "mailjet":
|
||||||
|
_send_booking_request_email_mailjet(payload)
|
||||||
|
return
|
||||||
|
|
||||||
|
if settings.mail_provider == "smtp":
|
||||||
|
_send_booking_request_email_smtp(payload)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise MailConfigurationError(f"Provider email non supportato: {settings.mail_provider}")
|
||||||
|
|
||||||
|
|
||||||
|
def _send_booking_request_email_smtp(payload: BookingRequestCreate) -> None:
|
||||||
if not settings.booking_mail_enabled:
|
if not settings.booking_mail_enabled:
|
||||||
raise MailConfigurationError("Configurazione SMTP incompleta.")
|
raise MailConfigurationError("Configurazione SMTP incompleta.")
|
||||||
|
|
||||||
@@ -81,3 +115,69 @@ def send_booking_request_email(payload: BookingRequestCreate) -> None:
|
|||||||
server.starttls()
|
server.starttls()
|
||||||
server.login(settings.smtp_username, settings.smtp_password)
|
server.login(settings.smtp_username, settings.smtp_password)
|
||||||
server.send_message(message)
|
server.send_message(message)
|
||||||
|
|
||||||
|
|
||||||
|
def _send_booking_request_email_mailjet(payload: BookingRequestCreate) -> None:
|
||||||
|
mailjet_payload = {
|
||||||
|
"Messages": [
|
||||||
|
{
|
||||||
|
"From": {
|
||||||
|
"Email": settings.booking_email_from,
|
||||||
|
"Name": settings.booking_email_from_name,
|
||||||
|
},
|
||||||
|
"To": [
|
||||||
|
{
|
||||||
|
"Email": settings.booking_email_to,
|
||||||
|
"Name": "Clinica Veterinaria Formiginese",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ReplyTo": {
|
||||||
|
"Email": payload.email,
|
||||||
|
"Name": payload.name,
|
||||||
|
},
|
||||||
|
"Subject": _build_booking_subject(payload),
|
||||||
|
"TextPart": _build_booking_text(payload),
|
||||||
|
"HTMLPart": _build_booking_html(payload),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.mailjet_sandbox_mode:
|
||||||
|
mailjet_payload["SandboxMode"] = True
|
||||||
|
|
||||||
|
auth_token = base64.b64encode(
|
||||||
|
f"{settings.mailjet_api_key}:{settings.mailjet_api_secret}".encode("utf-8")
|
||||||
|
).decode("ascii")
|
||||||
|
request = Request(
|
||||||
|
settings.mailjet_api_url,
|
||||||
|
data=json.dumps(mailjet_payload).encode("utf-8"),
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Authorization": f"Basic {auth_token}",
|
||||||
|
},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urlopen(request, timeout=30) as response:
|
||||||
|
response_body = response.read().decode("utf-8")
|
||||||
|
response_status = response.status
|
||||||
|
except HTTPError as exc:
|
||||||
|
error_body = exc.read().decode("utf-8", errors="replace")
|
||||||
|
raise MailDeliveryError(f"Mailjet API HTTP {exc.code}: {error_body[:500]}") from exc
|
||||||
|
except URLError as exc:
|
||||||
|
raise MailDeliveryError(f"Mailjet API connection error: {exc.reason}") from exc
|
||||||
|
|
||||||
|
if response_status < 200 or response_status >= 300:
|
||||||
|
raise MailDeliveryError(f"Mailjet API HTTP {response_status}: {response_body[:500]}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_payload = json.loads(response_body)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise MailDeliveryError(f"Mailjet API response non JSON: {response_body[:500]}") from exc
|
||||||
|
|
||||||
|
messages = response_payload.get("Messages", [])
|
||||||
|
message_status = messages[0].get("Status") if messages else None
|
||||||
|
if message_status != "success":
|
||||||
|
raise MailDeliveryError(f"Mailjet API delivery status {message_status}: {response_body[:500]}")
|
||||||
|
|||||||
Reference in New Issue
Block a user