Fix some issues
This commit is contained in:
545
services/notification/app/api/public_contact.py
Normal file
545
services/notification/app/api/public_contact.py
Normal file
@@ -0,0 +1,545 @@
|
||||
# ================================================================
|
||||
# services/notification/app/api/public_contact.py
|
||||
# ================================================================
|
||||
"""
|
||||
Public contact form API endpoints
|
||||
Handles contact, feedback, and prelaunch email submissions
|
||||
These endpoints are public (no authentication required)
|
||||
"""
|
||||
|
||||
import structlog
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
from typing import Optional, Literal
|
||||
from datetime import datetime
|
||||
|
||||
from app.services.email_service import EmailService
|
||||
from app.core.config import settings
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
router = APIRouter(prefix="/api/v1/public", tags=["public-contact"])
|
||||
|
||||
# ================================================================
|
||||
# PYDANTIC MODELS
|
||||
# ================================================================
|
||||
|
||||
class ContactFormRequest(BaseModel):
|
||||
"""Contact form submission request"""
|
||||
name: str = Field(..., min_length=2, max_length=100)
|
||||
email: EmailStr
|
||||
phone: Optional[str] = Field(None, max_length=20)
|
||||
bakery_name: Optional[str] = Field(None, max_length=100)
|
||||
type: Literal["general", "technical", "sales", "feedback"] = "general"
|
||||
subject: str = Field(..., min_length=5, max_length=200)
|
||||
message: str = Field(..., min_length=10, max_length=5000)
|
||||
|
||||
|
||||
class FeedbackFormRequest(BaseModel):
|
||||
"""Feedback form submission request"""
|
||||
name: str = Field(..., min_length=2, max_length=100)
|
||||
email: EmailStr
|
||||
category: Literal["suggestion", "bug", "feature", "praise", "complaint"]
|
||||
title: str = Field(..., min_length=5, max_length=200)
|
||||
description: str = Field(..., min_length=10, max_length=5000)
|
||||
rating: Optional[int] = Field(None, ge=1, le=5)
|
||||
|
||||
|
||||
class PrelaunchEmailRequest(BaseModel):
|
||||
"""Prelaunch email subscription request"""
|
||||
email: EmailStr
|
||||
|
||||
|
||||
class ContactFormResponse(BaseModel):
|
||||
"""Response for form submissions"""
|
||||
success: bool
|
||||
message: str
|
||||
|
||||
|
||||
# ================================================================
|
||||
# EMAIL TEMPLATES
|
||||
# ================================================================
|
||||
|
||||
CONTACT_EMAIL_TEMPLATE_TEXT = """
|
||||
Nuevo mensaje de contacto recibido
|
||||
|
||||
==============================================
|
||||
DATOS DEL CONTACTO
|
||||
==============================================
|
||||
|
||||
Nombre: {name}
|
||||
Email: {email}
|
||||
Teléfono: {phone}
|
||||
Panadería: {bakery_name}
|
||||
Tipo de consulta: {type_label}
|
||||
|
||||
==============================================
|
||||
ASUNTO
|
||||
==============================================
|
||||
{subject}
|
||||
|
||||
==============================================
|
||||
MENSAJE
|
||||
==============================================
|
||||
{message}
|
||||
|
||||
==============================================
|
||||
Recibido: {timestamp}
|
||||
"""
|
||||
|
||||
CONTACT_EMAIL_TEMPLATE_HTML = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }}
|
||||
.container {{ max-width: 600px; margin: 0 auto; background-color: #ffffff; }}
|
||||
.header {{ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); padding: 30px; text-align: center; }}
|
||||
.header h1 {{ color: white; margin: 0; font-size: 24px; }}
|
||||
.content {{ padding: 30px; }}
|
||||
.info-box {{ background-color: #fef3c7; border-left: 4px solid #f59e0b; padding: 15px; margin: 20px 0; }}
|
||||
.info-row {{ display: flex; margin-bottom: 10px; }}
|
||||
.info-label {{ font-weight: bold; color: #92400e; min-width: 120px; }}
|
||||
.info-value {{ color: #374151; }}
|
||||
.message-box {{ background-color: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 20px; margin: 20px 0; }}
|
||||
.message-box h3 {{ color: #1f2937; margin-top: 0; }}
|
||||
.footer {{ background-color: #f3f4f6; padding: 20px; text-align: center; font-size: 12px; color: #6b7280; }}
|
||||
.type-badge {{ display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; }}
|
||||
.type-general {{ background-color: #dbeafe; color: #1e40af; }}
|
||||
.type-technical {{ background-color: #fce7f3; color: #9d174d; }}
|
||||
.type-sales {{ background-color: #d1fae5; color: #065f46; }}
|
||||
.type-feedback {{ background-color: #e0e7ff; color: #3730a3; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Nuevo Mensaje de Contacto</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="info-box">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Nombre:</span>
|
||||
<span class="info-value">{name}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Email:</span>
|
||||
<span class="info-value"><a href="mailto:{email}">{email}</a></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Teléfono:</span>
|
||||
<span class="info-value">{phone}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Panadería:</span>
|
||||
<span class="info-value">{bakery_name}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Tipo:</span>
|
||||
<span class="info-value"><span class="type-badge type-{type}">{type_label}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-box">
|
||||
<h3>{subject}</h3>
|
||||
<p style="white-space: pre-wrap; color: #374151;">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
Recibido: {timestamp}<br>
|
||||
BakeWise - Sistema de Contacto
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
FEEDBACK_EMAIL_TEMPLATE_TEXT = """
|
||||
Nuevo feedback recibido
|
||||
|
||||
==============================================
|
||||
DATOS DEL USUARIO
|
||||
==============================================
|
||||
|
||||
Nombre: {name}
|
||||
Email: {email}
|
||||
Categoría: {category_label}
|
||||
Valoración: {rating_display}
|
||||
|
||||
==============================================
|
||||
TÍTULO
|
||||
==============================================
|
||||
{title}
|
||||
|
||||
==============================================
|
||||
DESCRIPCIÓN
|
||||
==============================================
|
||||
{description}
|
||||
|
||||
==============================================
|
||||
Recibido: {timestamp}
|
||||
"""
|
||||
|
||||
FEEDBACK_EMAIL_TEMPLATE_HTML = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }}
|
||||
.container {{ max-width: 600px; margin: 0 auto; background-color: #ffffff; }}
|
||||
.header {{ background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%); padding: 30px; text-align: center; }}
|
||||
.header h1 {{ color: white; margin: 0; font-size: 24px; }}
|
||||
.content {{ padding: 30px; }}
|
||||
.info-box {{ background-color: #ede9fe; border-left: 4px solid #8b5cf6; padding: 15px; margin: 20px 0; }}
|
||||
.info-row {{ display: flex; margin-bottom: 10px; }}
|
||||
.info-label {{ font-weight: bold; color: #5b21b6; min-width: 120px; }}
|
||||
.info-value {{ color: #374151; }}
|
||||
.message-box {{ background-color: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 20px; margin: 20px 0; }}
|
||||
.message-box h3 {{ color: #1f2937; margin-top: 0; }}
|
||||
.footer {{ background-color: #f3f4f6; padding: 20px; text-align: center; font-size: 12px; color: #6b7280; }}
|
||||
.category-badge {{ display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; }}
|
||||
.category-suggestion {{ background-color: #dbeafe; color: #1e40af; }}
|
||||
.category-bug {{ background-color: #fee2e2; color: #991b1b; }}
|
||||
.category-feature {{ background-color: #d1fae5; color: #065f46; }}
|
||||
.category-praise {{ background-color: #fef3c7; color: #92400e; }}
|
||||
.category-complaint {{ background-color: #fce7f3; color: #9d174d; }}
|
||||
.stars {{ color: #f59e0b; font-size: 18px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Nuevo Feedback Recibido</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="info-box">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Nombre:</span>
|
||||
<span class="info-value">{name}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Email:</span>
|
||||
<span class="info-value"><a href="mailto:{email}">{email}</a></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Categoría:</span>
|
||||
<span class="info-value"><span class="category-badge category-{category}">{category_label}</span></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Valoración:</span>
|
||||
<span class="info-value stars">{rating_display}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-box">
|
||||
<h3>{title}</h3>
|
||||
<p style="white-space: pre-wrap; color: #374151;">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
Recibido: {timestamp}<br>
|
||||
BakeWise - Sistema de Feedback
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
PRELAUNCH_EMAIL_TEMPLATE_TEXT = """
|
||||
Nueva suscripción de pre-lanzamiento
|
||||
|
||||
==============================================
|
||||
NUEVO INTERESADO
|
||||
==============================================
|
||||
|
||||
Email: {email}
|
||||
|
||||
El usuario ha mostrado interés en BakeWise y quiere ser notificado cuando se lance oficialmente.
|
||||
|
||||
==============================================
|
||||
Recibido: {timestamp}
|
||||
"""
|
||||
|
||||
PRELAUNCH_EMAIL_TEMPLATE_HTML = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }}
|
||||
.container {{ max-width: 600px; margin: 0 auto; background-color: #ffffff; }}
|
||||
.header {{ background: linear-gradient(135deg, #10b981 0%, #059669 100%); padding: 30px; text-align: center; }}
|
||||
.header h1 {{ color: white; margin: 0; font-size: 24px; }}
|
||||
.content {{ padding: 30px; text-align: center; }}
|
||||
.email-box {{ background-color: #d1fae5; border: 2px solid #10b981; border-radius: 12px; padding: 20px; margin: 20px 0; }}
|
||||
.email-box h2 {{ color: #065f46; margin: 0 0 10px 0; }}
|
||||
.email-box a {{ color: #059669; font-size: 18px; font-weight: bold; }}
|
||||
.footer {{ background-color: #f3f4f6; padding: 20px; text-align: center; font-size: 12px; color: #6b7280; }}
|
||||
.rocket {{ font-size: 48px; margin-bottom: 10px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Nueva Suscripción Pre-Lanzamiento</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="rocket">🚀</div>
|
||||
<p style="color: #374151; font-size: 16px;">Un nuevo usuario quiere ser notificado del lanzamiento de BakeWise</p>
|
||||
|
||||
<div class="email-box">
|
||||
<h2>Email del interesado:</h2>
|
||||
<a href="mailto:{email}">{email}</a>
|
||||
</div>
|
||||
|
||||
<p style="color: #6b7280;">Añade este email a la lista de espera para el lanzamiento oficial.</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
Recibido: {timestamp}<br>
|
||||
BakeWise - Lista de Espera
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
# ================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ================================================================
|
||||
|
||||
def get_type_label(type_code: str) -> str:
|
||||
"""Get human-readable label for contact type"""
|
||||
labels = {
|
||||
"general": "Consulta General",
|
||||
"technical": "Soporte Técnico",
|
||||
"sales": "Ventas",
|
||||
"feedback": "Feedback"
|
||||
}
|
||||
return labels.get(type_code, type_code)
|
||||
|
||||
|
||||
def get_category_label(category: str) -> str:
|
||||
"""Get human-readable label for feedback category"""
|
||||
labels = {
|
||||
"suggestion": "Sugerencia",
|
||||
"bug": "Error/Bug",
|
||||
"feature": "Nueva Funcionalidad",
|
||||
"praise": "Elogio",
|
||||
"complaint": "Queja"
|
||||
}
|
||||
return labels.get(category, category)
|
||||
|
||||
|
||||
def get_rating_display(rating: Optional[int]) -> str:
|
||||
"""Convert rating to star display"""
|
||||
if rating is None:
|
||||
return "N/A"
|
||||
return "★" * rating + "☆" * (5 - rating)
|
||||
|
||||
|
||||
# ================================================================
|
||||
# API ENDPOINTS
|
||||
# ================================================================
|
||||
|
||||
@router.post("/contact", response_model=ContactFormResponse)
|
||||
async def submit_contact_form(request: ContactFormRequest):
|
||||
"""
|
||||
Submit a contact form message.
|
||||
Sends an email to contact@bakewise.ai with the form data.
|
||||
"""
|
||||
try:
|
||||
email_service = EmailService()
|
||||
|
||||
timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
type_label = get_type_label(request.type)
|
||||
|
||||
# Format email content
|
||||
text_content = CONTACT_EMAIL_TEMPLATE_TEXT.format(
|
||||
name=request.name,
|
||||
email=request.email,
|
||||
phone=request.phone or "No proporcionado",
|
||||
bakery_name=request.bakery_name or "No proporcionado",
|
||||
type=request.type,
|
||||
type_label=type_label,
|
||||
subject=request.subject,
|
||||
message=request.message,
|
||||
timestamp=timestamp
|
||||
)
|
||||
|
||||
html_content = CONTACT_EMAIL_TEMPLATE_HTML.format(
|
||||
name=request.name,
|
||||
email=request.email,
|
||||
phone=request.phone or "No proporcionado",
|
||||
bakery_name=request.bakery_name or "No proporcionado",
|
||||
type=request.type,
|
||||
type_label=type_label,
|
||||
subject=request.subject,
|
||||
message=request.message,
|
||||
timestamp=timestamp
|
||||
)
|
||||
|
||||
# Send email to contact@bakewise.ai
|
||||
success = await email_service.send_email(
|
||||
to_email="contact@bakewise.ai",
|
||||
subject=f"[{type_label}] {request.subject}",
|
||||
text_content=text_content,
|
||||
html_content=html_content,
|
||||
reply_to=request.email
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info("Contact form submitted successfully",
|
||||
from_email=request.email,
|
||||
type=request.type,
|
||||
subject=request.subject)
|
||||
return ContactFormResponse(
|
||||
success=True,
|
||||
message="Mensaje enviado correctamente. Te responderemos pronto."
|
||||
)
|
||||
else:
|
||||
logger.error("Failed to send contact form email",
|
||||
from_email=request.email)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Error al enviar el mensaje. Por favor, inténtalo de nuevo."
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Contact form submission error", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Error interno. Por favor, inténtalo más tarde."
|
||||
)
|
||||
|
||||
|
||||
@router.post("/feedback", response_model=ContactFormResponse)
|
||||
async def submit_feedback_form(request: FeedbackFormRequest):
|
||||
"""
|
||||
Submit a feedback form.
|
||||
Sends an email to contact@bakewise.ai with the feedback data.
|
||||
"""
|
||||
try:
|
||||
email_service = EmailService()
|
||||
|
||||
timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
category_label = get_category_label(request.category)
|
||||
rating_display = get_rating_display(request.rating)
|
||||
|
||||
# Format email content
|
||||
text_content = FEEDBACK_EMAIL_TEMPLATE_TEXT.format(
|
||||
name=request.name,
|
||||
email=request.email,
|
||||
category=request.category,
|
||||
category_label=category_label,
|
||||
rating_display=rating_display,
|
||||
title=request.title,
|
||||
description=request.description,
|
||||
timestamp=timestamp
|
||||
)
|
||||
|
||||
html_content = FEEDBACK_EMAIL_TEMPLATE_HTML.format(
|
||||
name=request.name,
|
||||
email=request.email,
|
||||
category=request.category,
|
||||
category_label=category_label,
|
||||
rating_display=rating_display,
|
||||
title=request.title,
|
||||
description=request.description,
|
||||
timestamp=timestamp
|
||||
)
|
||||
|
||||
# Send email to contact@bakewise.ai
|
||||
success = await email_service.send_email(
|
||||
to_email="contact@bakewise.ai",
|
||||
subject=f"[Feedback - {category_label}] {request.title}",
|
||||
text_content=text_content,
|
||||
html_content=html_content,
|
||||
reply_to=request.email
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info("Feedback form submitted successfully",
|
||||
from_email=request.email,
|
||||
category=request.category,
|
||||
title=request.title)
|
||||
return ContactFormResponse(
|
||||
success=True,
|
||||
message="Gracias por tu feedback. Lo revisaremos pronto."
|
||||
)
|
||||
else:
|
||||
logger.error("Failed to send feedback form email",
|
||||
from_email=request.email)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Error al enviar el feedback. Por favor, inténtalo de nuevo."
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Feedback form submission error", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Error interno. Por favor, inténtalo más tarde."
|
||||
)
|
||||
|
||||
|
||||
@router.post("/prelaunch-subscribe", response_model=ContactFormResponse)
|
||||
async def submit_prelaunch_email(request: PrelaunchEmailRequest):
|
||||
"""
|
||||
Submit a prelaunch email subscription.
|
||||
Sends a notification email to contact@bakewise.ai.
|
||||
"""
|
||||
try:
|
||||
email_service = EmailService()
|
||||
|
||||
timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
|
||||
# Format email content
|
||||
text_content = PRELAUNCH_EMAIL_TEMPLATE_TEXT.format(
|
||||
email=request.email,
|
||||
timestamp=timestamp
|
||||
)
|
||||
|
||||
html_content = PRELAUNCH_EMAIL_TEMPLATE_HTML.format(
|
||||
email=request.email,
|
||||
timestamp=timestamp
|
||||
)
|
||||
|
||||
# Send email to contact@bakewise.ai
|
||||
success = await email_service.send_email(
|
||||
to_email="contact@bakewise.ai",
|
||||
subject=f"[Pre-Lanzamiento] Nueva suscripción: {request.email}",
|
||||
text_content=text_content,
|
||||
html_content=html_content,
|
||||
reply_to=request.email
|
||||
)
|
||||
|
||||
if success:
|
||||
logger.info("Prelaunch subscription submitted successfully",
|
||||
email=request.email)
|
||||
return ContactFormResponse(
|
||||
success=True,
|
||||
message="Te hemos apuntado a la lista de espera."
|
||||
)
|
||||
else:
|
||||
logger.error("Failed to send prelaunch subscription email",
|
||||
email=request.email)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Error al registrar tu email. Por favor, inténtalo de nuevo."
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Prelaunch subscription error", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Error interno. Por favor, inténtalo más tarde."
|
||||
)
|
||||
@@ -15,6 +15,7 @@ from app.api.notification_operations import router as notification_operations_ro
|
||||
from app.api.analytics import router as analytics_router
|
||||
from app.api.audit import router as audit_router
|
||||
from app.api.whatsapp_webhooks import router as whatsapp_webhooks_router
|
||||
from app.api.public_contact import router as public_contact_router
|
||||
from app.services.sse_service import SSEService
|
||||
from app.services.notification_orchestrator import NotificationOrchestrator
|
||||
from app.services.email_service import EmailService
|
||||
@@ -306,6 +307,7 @@ service.setup_custom_endpoints()
|
||||
# where {notification_id} would match literal paths like "audit-logs"
|
||||
service.add_router(audit_router, tags=["audit-logs"])
|
||||
service.add_router(whatsapp_webhooks_router, tags=["whatsapp-webhooks"])
|
||||
service.add_router(public_contact_router, tags=["public-contact"])
|
||||
service.add_router(notification_operations_router, tags=["notification-operations"])
|
||||
service.add_router(analytics_router, tags=["notifications-analytics"])
|
||||
service.add_router(notification_router, tags=["notifications"])
|
||||
|
||||
@@ -480,12 +480,32 @@ async def trigger_failure_notifications(notification_service: any, tenant_id: UU
|
||||
}
|
||||
|
||||
html_content = template.render(**template_vars)
|
||||
text_content = f"Equipment failure alert: {equipment.name} - {failure_data.get('failureType', 'Unknown')}"
|
||||
|
||||
# Send via notification service (which will handle the actual email sending)
|
||||
# This is a simplified approach - in production you'd want to get manager emails from DB
|
||||
logger.info("Failure notifications triggered (template rendered)",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id))
|
||||
# Send via notification service API
|
||||
if notification_service:
|
||||
result = await notification_service.send_email(
|
||||
tenant_id=str(tenant_id),
|
||||
to_email="managers@bakeryia.com", # Should be configured from DB in production
|
||||
subject=f"🚨 Fallo de Equipo: {equipment.name}",
|
||||
message=text_content,
|
||||
html_content=html_content,
|
||||
priority="high"
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info("Failure notifications sent via notification service",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id),
|
||||
notification_id=result.get('notification_id'))
|
||||
else:
|
||||
logger.error("Failed to send failure notifications via notification service",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id))
|
||||
else:
|
||||
logger.warning("Notification service not available, failure notifications not sent",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error triggering failure notifications",
|
||||
@@ -523,11 +543,32 @@ async def trigger_repair_notifications(notification_service: any, tenant_id: UUI
|
||||
}
|
||||
|
||||
html_content = template.render(**template_vars)
|
||||
text_content = f"Equipment repair completed: {equipment.name} - {repair_data.get('repairDescription', 'Repair completed')}"
|
||||
|
||||
# Send via notification service
|
||||
logger.info("Repair notifications triggered (template rendered)",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id))
|
||||
# Send via notification service API
|
||||
if notification_service:
|
||||
result = await notification_service.send_email(
|
||||
tenant_id=str(tenant_id),
|
||||
to_email="managers@bakeryia.com", # Should be configured from DB in production
|
||||
subject=f"✅ Equipo Reparado: {equipment.name}",
|
||||
message=text_content,
|
||||
html_content=html_content,
|
||||
priority="normal"
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info("Repair notifications sent via notification service",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id),
|
||||
notification_id=result.get('notification_id'))
|
||||
else:
|
||||
logger.error("Failed to send repair notifications via notification service",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id))
|
||||
else:
|
||||
logger.warning("Notification service not available, repair notifications not sent",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error triggering repair notifications",
|
||||
@@ -565,14 +606,35 @@ async def send_support_contact_notification(notification_service: any, tenant_id
|
||||
}
|
||||
|
||||
html_content = template.render(**template_vars)
|
||||
text_content = f"Equipment failure alert: {equipment.name} - {failure_data.get('failureType', 'Unknown')}"
|
||||
|
||||
# TODO: Actually send email via notification service
|
||||
# For now, just log that we would send to the support email
|
||||
logger.info("Support contact notification prepared (would send to support)",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id),
|
||||
support_email=support_email,
|
||||
subject=f"🚨 URGENTE: Fallo de Equipo - {equipment.name}")
|
||||
# Send via notification service API
|
||||
if notification_service and support_email:
|
||||
result = await notification_service.send_email(
|
||||
tenant_id=str(tenant_id),
|
||||
to_email=support_email,
|
||||
subject=f"🚨 URGENTE: Fallo de Equipo - {equipment.name}",
|
||||
message=text_content,
|
||||
html_content=html_content,
|
||||
priority="high"
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info("Support contact notification sent via notification service",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id),
|
||||
support_email=support_email,
|
||||
notification_id=result.get('notification_id'))
|
||||
else:
|
||||
logger.error("Failed to send support contact notification via notification service",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id),
|
||||
support_email=support_email)
|
||||
else:
|
||||
logger.warning("Notification service not available or no support email provided",
|
||||
equipment_id=str(equipment.id),
|
||||
tenant_id=str(tenant_id),
|
||||
support_email=support_email)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error sending support contact notification",
|
||||
|
||||
@@ -22,9 +22,10 @@ class AlertEventConsumer:
|
||||
Handles email and Slack notifications for critical alerts
|
||||
"""
|
||||
|
||||
def __init__(self, db_session: AsyncSession):
|
||||
def __init__(self, db_session: AsyncSession, notification_client: Optional[any] = None):
|
||||
self.db_session = db_session
|
||||
self.notification_config = self._load_notification_config()
|
||||
self.notification_client = notification_client
|
||||
|
||||
def _load_notification_config(self) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -451,7 +452,7 @@ class AlertEventConsumer:
|
||||
data: Dict[str, Any]
|
||||
) -> bool:
|
||||
"""
|
||||
Send email notification
|
||||
Send email notification using notification service API
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
@@ -466,42 +467,77 @@ class AlertEventConsumer:
|
||||
logger.debug("Email notifications disabled")
|
||||
return False
|
||||
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
# Use notification service client if available
|
||||
if self.notification_client:
|
||||
# Build email content
|
||||
subject = self._format_email_subject(notification_type, data)
|
||||
body = self._format_email_body(notification_type, data)
|
||||
|
||||
# Build email content
|
||||
subject = self._format_email_subject(notification_type, data)
|
||||
body = self._format_email_body(notification_type, data)
|
||||
# Send via notification service API
|
||||
result = await self.notification_client.send_email(
|
||||
tenant_id=tenant_id,
|
||||
to_email=", ".join(self.notification_config['email']['recipients']),
|
||||
subject=subject,
|
||||
message="Supplier alert notification", # Plain text fallback
|
||||
html_content=body,
|
||||
priority="high" if data.get('severity') == 'critical' else "normal"
|
||||
)
|
||||
|
||||
# Create message
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = self.notification_config['email']['from_address']
|
||||
msg['To'] = ', '.join(self.notification_config['email']['recipients'])
|
||||
if result:
|
||||
logger.info(
|
||||
"Email notification sent via notification service",
|
||||
tenant_id=tenant_id,
|
||||
notification_type=notification_type,
|
||||
recipients=len(self.notification_config['email']['recipients'])
|
||||
)
|
||||
return True
|
||||
else:
|
||||
logger.error(
|
||||
"Notification service failed to send email",
|
||||
tenant_id=tenant_id,
|
||||
notification_type=notification_type
|
||||
)
|
||||
return False
|
||||
else:
|
||||
# Fallback to direct SMTP for backward compatibility
|
||||
logger.warning("Notification client not available, falling back to direct SMTP")
|
||||
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
# Attach HTML body
|
||||
html_part = MIMEText(body, 'html')
|
||||
msg.attach(html_part)
|
||||
# Build email content
|
||||
subject = self._format_email_subject(notification_type, data)
|
||||
body = self._format_email_body(notification_type, data)
|
||||
|
||||
# Send email
|
||||
smtp_config = self.notification_config['email']
|
||||
with smtplib.SMTP(smtp_config['smtp_host'], smtp_config['smtp_port']) as server:
|
||||
if smtp_config['use_tls']:
|
||||
server.starttls()
|
||||
# Create message
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = self.notification_config['email']['from_address']
|
||||
msg['To'] = ', '.join(self.notification_config['email']['recipients'])
|
||||
|
||||
if smtp_config['smtp_username'] and smtp_config['smtp_password']:
|
||||
server.login(smtp_config['smtp_username'], smtp_config['smtp_password'])
|
||||
# Attach HTML body
|
||||
html_part = MIMEText(body, 'html')
|
||||
msg.attach(html_part)
|
||||
|
||||
server.send_message(msg)
|
||||
# Send email
|
||||
smtp_config = self.notification_config['email']
|
||||
with smtplib.SMTP(smtp_config['smtp_host'], smtp_config['smtp_port']) as server:
|
||||
if smtp_config['use_tls']:
|
||||
server.starttls()
|
||||
|
||||
logger.info(
|
||||
"Email notification sent",
|
||||
tenant_id=tenant_id,
|
||||
notification_type=notification_type,
|
||||
recipients=len(self.notification_config['email']['recipients'])
|
||||
)
|
||||
return True
|
||||
if smtp_config['smtp_username'] and smtp_config['smtp_password']:
|
||||
server.login(smtp_config['smtp_username'], smtp_config['smtp_password'])
|
||||
|
||||
server.send_message(msg)
|
||||
|
||||
logger.info(
|
||||
"Email notification sent via direct SMTP (fallback)",
|
||||
tenant_id=tenant_id,
|
||||
notification_type=notification_type,
|
||||
recipients=len(self.notification_config['email']['recipients'])
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
@@ -785,6 +821,6 @@ class AlertEventConsumer:
|
||||
|
||||
|
||||
# Factory function for creating consumer instance
|
||||
def create_alert_event_consumer(db_session: AsyncSession) -> AlertEventConsumer:
|
||||
def create_alert_event_consumer(db_session: AsyncSession, notification_client: Optional[any] = None) -> AlertEventConsumer:
|
||||
"""Create alert event consumer instance"""
|
||||
return AlertEventConsumer(db_session)
|
||||
return AlertEventConsumer(db_session, notification_client)
|
||||
|
||||
Reference in New Issue
Block a user