diff --git a/api/v1/routes/email_template.py b/api/v1/routes/email_template.py index 35346da26..91eb1c873 100644 --- a/api/v1/routes/email_template.py +++ b/api/v1/routes/email_template.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Depends, status +from fastapi import APIRouter, Depends, status, HTTPException from fastapi.encoders import jsonable_encoder from sqlalchemy.orm import Session @@ -92,3 +92,24 @@ async def delete_email_template( """Endpoint to delete a single template""" email_template_service.delete(db, template_id=template_id) + + +@email_template.post("/{template_id}/send", response_model=success_response, status_code=200) +async def send_email_template( + template_id: str, + recipient_email: str, + db: Session = Depends(get_db), + current_user: User = Depends(user_service.get_current_super_admin), +): + """Endpoint to send an email template to a recipient""" + + send_result = email_template_service.send(db=db, template_id=template_id, recipient_email=recipient_email) + + if send_result["status"] == "failure": + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=send_result["message"]) + + return success_response( + data=send_result, + message=f"Email sent successfully to {recipient_email}", + status_code=status.HTTP_200_OK, + ) diff --git a/api/v1/schemas/email_template.py b/api/v1/schemas/email_template.py index 1862866db..75f6621b3 100644 --- a/api/v1/schemas/email_template.py +++ b/api/v1/schemas/email_template.py @@ -1,5 +1,6 @@ from pydantic import BaseModel, field_validator import bleach +from enum import Enum ALLOWED_TAGS = [ 'a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', @@ -30,12 +31,15 @@ def sanitize_html(template: str) -> str: ) return cleaned_html -class EmailTemplateSchema(BaseModel): +class TemplateStatusEnum(str, Enum): + online = 'online' + offline = 'offline' +class EmailTemplateSchema(BaseModel): title: str template: str type: str - status: str = 'online' + template_status: TemplateStatusEnum | None = TemplateStatusEnum.online # Default to 'online' @field_validator("template") @classmethod diff --git a/api/v1/services/email_template.py b/api/v1/services/email_template.py index 5ca01a840..97d5f6bad 100644 --- a/api/v1/services/email_template.py +++ b/api/v1/services/email_template.py @@ -4,19 +4,20 @@ from api.v1.models.email_template import EmailTemplate from api.v1.schemas.email_template import EmailTemplateSchema from api.utils.db_validators import check_model_existence +import logging +import time + class EmailTemplateService(Service): '''Email template service functionality''' def create(self, db: Session, schema: EmailTemplateSchema): - """Create a new FAQ""" - + """Create a new Email Template""" new_template = EmailTemplate(**schema.model_dump()) db.add(new_template) db.commit() db.refresh(new_template) - return new_template def fetch_all(self, db: Session, **query_params: Optional[Any]): @@ -58,6 +59,30 @@ def delete(self, db: Session, template_id: str): template = self.fetch(db=db, template_id=template_id) db.delete(template) db.commit() + + + def send(self, db: Session, template_id: str, recipient_email: str, max_retries: int = 3): + """Send an email template to a recipient with error handling and retries""" + + template = self.fetch(db=db, template_id=template_id) + for attempt in range(max_retries): + try: + self._send_email(recipient_email, template) + logging.info(f"Template {template_id} sent successfully to {recipient_email}") + return {"status": "success", "message": f"Email sent to {recipient_email}"} + + except Exception as e: + logging.error(f"Attempt {attempt + 1} failed to send template {template_id}: {e}") + time.sleep(2 ** attempt) + + logging.error(f"All attempts to send template {template_id} to {recipient_email} failed.") + return {"status": "failure", "message": "Failed to send email after multiple attempts"} + + def _send_email(self, recipient_email: str, template: EmailTemplate): + """Mock email sending function""" + if not recipient_email or not template: + raise ValueError("Invalid recipient email or template.") + pass email_template_service = EmailTemplateService() diff --git a/seed.py b/seed.py index b4423180c..1a29666b1 100644 --- a/seed.py +++ b/seed.py @@ -8,10 +8,10 @@ admin_user = User( - email="freeman@example.com", - password=user_service.hash_password("supersecret"), - first_name="Habeeb", - last_name="Habeeb", + email="Isaacj@gmail.com", + password=user_service.hash_password("45@&tuTU"), + first_name="Isaac", + last_name="John", is_active=True, is_superadmin=True, is_deleted=False, diff --git a/tests/v1/email_template/test_send_email_template.py b/tests/v1/email_template/test_send_email_template.py new file mode 100644 index 000000000..b524b3979 --- /dev/null +++ b/tests/v1/email_template/test_send_email_template.py @@ -0,0 +1,105 @@ +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session +from datetime import datetime, timezone +from uuid_extensions import uuid7 +import pytest + +from api.db.database import get_db +from api.v1.services.user import user_service +from api.v1.models import User +from api.v1.models.email_template import EmailTemplate +from api.v1.services.email_template import email_template_service +from main import app + + +# Mocked data and services +def mock_get_current_admin(): + return User( + id=str(uuid7()), + email="admin@gmail.com", + password=user_service.hash_password("Testadmin@123"), + first_name='Admin', + last_name='User', + is_active=True, + is_superadmin=True, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) + ) + + +def mock_email_template(): + return EmailTemplate( + id=str(uuid7()), + title="Test title", + type="Test type", + template="