diff --git a/api/core/dependencies/email/templates/faq-feedback.html b/api/core/dependencies/email/templates/faq-feedback.html
new file mode 100644
index 000000000..c0aa333f6
--- /dev/null
+++ b/api/core/dependencies/email/templates/faq-feedback.html
@@ -0,0 +1,51 @@
+{% extends 'admin-base.html' %}
+
+{% block title %}
+ Magic Link
+{% endblock %}
+
+{% block content %}
+
+
+
+
User Details
+
+ -
+ Full Name: {{ full_name }}
+ -
+
+ Email: {{ email }}
+
+
+
Message
+
+ {{ message }}
+
+
+
+{% endblock %}
+
diff --git a/api/utils/send_mail.py b/api/utils/send_mail.py
index 26da36537..c027f0c67 100644
--- a/api/utils/send_mail.py
+++ b/api/utils/send_mail.py
@@ -57,4 +57,26 @@ def send_mail_handler(sender, reciever, html, subject):
with smtplib.SMTP_SSL(settings.MAIL_SERVER, settings.MAIL_PORT) as server:
server.login(settings.MAIL_USERNAME, settings.MAIL_PASSWORD)
- server.sendmail(sender, reciever, message.as_string())
\ No newline at end of file
+ server.sendmail(sender, reciever, message.as_string())
+
+
+def send_faq_inquiry_mail(context: dict):
+ from main import email_templates
+ sender_email = settings.MAIL_USERNAME
+ receiver_email = context.get('email')
+ password = settings.MAIL_PASSWORD
+
+ html = email_templates.get_template("faq-feedback.html").render(context)
+
+ message = MIMEMultipart("alternative")
+ message["Subject"] = "We've received your inquiry"
+ message["From"] = sender_email
+ message["To"] = receiver_email
+
+ part = MIMEText(html, "html")
+
+ message.attach(part)
+
+ with smtplib.SMTP_SSL(settings.MAIL_SERVER, settings.MAIL_PORT) as server:
+ server.login(sender_email, password)
+ server.sendmail(sender_email, receiver_email, message.as_string())
diff --git a/api/v1/models/__init__.py b/api/v1/models/__init__.py
index 2fb26c368..943c3e100 100644
--- a/api/v1/models/__init__.py
+++ b/api/v1/models/__init__.py
@@ -28,3 +28,4 @@
from api.v1.models.privacy import PrivacyPolicy
from api.v1.models.terms import TermsAndConditions
from api.v1.models.reset_password_token import ResetPasswordToken
+from api.v1.models.faq_inquiries import FAQInquiries
diff --git a/api/v1/models/faq_inquiries.py b/api/v1/models/faq_inquiries.py
new file mode 100644
index 000000000..428b02a37
--- /dev/null
+++ b/api/v1/models/faq_inquiries.py
@@ -0,0 +1,10 @@
+from sqlalchemy import Column, String, Text
+from api.v1.models.base_model import BaseTableModel
+
+
+class FAQInquiries(BaseTableModel):
+ __tablename__ = "faq_inquiries"
+
+ email = Column(String, nullable=False)
+ full_name = Column(String, nullable=False)
+ message = Column(Text, nullable=False)
diff --git a/api/v1/routes/__init__.py b/api/v1/routes/__init__.py
index ccea261a2..8b7ee2a7e 100644
--- a/api/v1/routes/__init__.py
+++ b/api/v1/routes/__init__.py
@@ -3,6 +3,7 @@
from api.v1.routes.team import team
from fastapi import APIRouter
from api.v1.routes.auth import auth
+from api.v1.routes.faq_inquiries import faq_inquiries
from api.v1.routes.newsletter import newsletter, news_sub
from api.v1.routes.user import user_router
from api.v1.routes.product import product, non_organisation_product
@@ -48,6 +49,7 @@
api_version_one = APIRouter(prefix="/api/v1")
api_version_one.include_router(auth)
+api_version_one.include_router(faq_inquiries)
api_version_one.include_router(google_auth)
api_version_one.include_router(fb_auth)
api_version_one.include_router(pwd_reset)
diff --git a/api/v1/routes/faq_inquiries.py b/api/v1/routes/faq_inquiries.py
new file mode 100644
index 000000000..47ac2291b
--- /dev/null
+++ b/api/v1/routes/faq_inquiries.py
@@ -0,0 +1,66 @@
+from fastapi import APIRouter, status, BackgroundTasks, Depends
+from api.core.responses import SUCCESS
+from api.db.database import get_db
+from api.utils.send_mail import send_faq_inquiry_mail
+from api.utils.success_response import success_response
+from api.v1.models.user import User
+from api.v1.schemas.faq_inquiries import CreateFAQInquiry
+from api.v1.services.faq_inquiries import faq_inquiries_service
+from api.v1.services.user import user_service
+from sqlalchemy.orm import Session
+from typing import Annotated
+
+faq_inquiries = APIRouter(prefix="/faq-inquiries", tags=["FAQ-Inquiries"])
+
+# CREATE
+@faq_inquiries.post(
+ "",
+ response_model=success_response,
+ status_code=status.HTTP_201_CREATED,
+ responses={
+ 201: {"description": "FAQ Inquiry created successfully"},
+ 422: {"description": "Validation Error"},
+ },
+)
+async def create_faq_inquiry(
+ data: CreateFAQInquiry, db: Annotated[Session, Depends(get_db)],
+ background_tasks: BackgroundTasks,
+):
+ """Add a new FAQ Inquiry."""
+ new_faq_inquiry = faq_inquiries_service.create(db, data)
+
+ # Send email to admin
+ background_tasks.add_task(
+ send_faq_inquiry_mail,
+ context={
+ "full_name": new_faq_inquiry.full_name,
+ "email": new_faq_inquiry.email,
+ "message": new_faq_inquiry.message,
+ }
+ )
+
+ response = success_response(
+ message=SUCCESS,
+ data={"id": new_faq_inquiry.id},
+ status_code=status.HTTP_201_CREATED,
+ )
+ return response
+
+# READ
+@faq_inquiries.get(
+ "",
+ response_model=success_response,
+ status_code=200
+)
+async def get_all_faq_inquiries(
+ db: Annotated[Session, Depends(get_db)],
+ admin: User = Depends(user_service.get_current_super_admin),
+):
+ """Fetch all FAQ Inquiries."""
+ faq_inquiries = faq_inquiries_service.fetch_all(db)
+ response = success_response(
+ message=SUCCESS,
+ data=faq_inquiries,
+ status_code=status.HTTP_200_OK,
+ )
+ return response
\ No newline at end of file
diff --git a/api/v1/schemas/faq_inquiries.py b/api/v1/schemas/faq_inquiries.py
new file mode 100644
index 000000000..d636eb0e9
--- /dev/null
+++ b/api/v1/schemas/faq_inquiries.py
@@ -0,0 +1,8 @@
+from pydantic import BaseModel, EmailStr, Field
+
+class CreateFAQInquiry(BaseModel):
+ """Validate the FAQ Inquiry form data."""
+
+ full_name: str = Field(..., example="John Doe")
+ email: EmailStr = Field(..., example="johndoe@gmail.com")
+ message: str = Field(..., example="I have a question about the product.")
\ No newline at end of file
diff --git a/api/v1/services/faq_inquiries.py b/api/v1/services/faq_inquiries.py
new file mode 100644
index 000000000..a3d3269b1
--- /dev/null
+++ b/api/v1/services/faq_inquiries.py
@@ -0,0 +1,79 @@
+from fastapi import Depends
+from api.core.base.services import Service
+from api.v1.models.faq_inquiries import FAQInquiries
+from api.v1.schemas.faq_inquiries import CreateFAQInquiry
+from sqlalchemy.orm import Session
+from typing import Annotated, Optional, Any
+from api.v1.routes.faq_inquiries import get_db
+
+
+class FAQInquiryService(Service):
+ """FAQ Inquiry Service."""
+
+ def __init__(self) -> None:
+ self.adabtingMapper = {
+ "full_name": "full_name",
+ "email": "email",
+ "message": "message",
+ }
+ super().__init__()
+
+ # ------------ CRUD functions ------------ #
+ # CREATE
+ def create(self, db: Annotated[Session, Depends(get_db)], data: CreateFAQInquiry):
+ """Create a new FAQ Inquiry."""
+ faq_inquiry = FAQInquiries(
+ full_name=getattr(data, self.adabtingMapper["full_name"]),
+ email=getattr(data, self.adabtingMapper["email"]),
+ message=getattr(data, self.adabtingMapper["message"]),
+ )
+ db.add(faq_inquiry)
+ db.commit()
+ db.refresh(faq_inquiry)
+ return faq_inquiry
+
+ # READ
+ def fetch_all(self, db: Session, **query_params: Optional[Any]):
+ """Fetch all submisions with option to search using query parameters"""
+
+ query = db.query(FAQInquiries)
+
+ # Enable filter by query parameter
+ if query_params:
+ for column, value in query_params.items():
+ if hasattr(FAQInquiries, column) and value:
+ query = query.filter(getattr(FAQInquiries, column).ilike(f"%{value}%"))
+
+ return query.all()
+
+ def fetch(self, db: Session, id: str):
+ """Fetches a faq_inquiry by id"""
+
+ faq_inquiry = db.query(FAQInquiries).get(id)
+ return faq_inquiry
+
+ def fetch_by_email(self, db: Session, email: str):
+ """Fetches a faq_inquiry by email"""
+
+ faq_inquiry = db.query(FAQInquiries).filter(FAQInquiries.email == email).first()
+ return faq_inquiry
+
+ def delete(self, db: Session, id: str):
+ """Delete a faq_inquiry by id"""
+
+ faq_inquiry = db.query(FAQInquiries).get(id)
+ db.delete(faq_inquiry)
+ db.commit()
+ return faq_inquiry
+
+ def update(self, db: Session, id: str, data: CreateFAQInquiry):
+ faq_inquiry = db.query(FAQInquiries).get(id)
+ faq_inquiry.full_name = data.full_name
+ faq_inquiry.email = data.email
+ faq_inquiry.message = data.message
+ db.commit()
+ db.refresh(faq_inquiry)
+ return faq_inquiry
+
+
+faq_inquiries_service = FAQInquiryService()
\ No newline at end of file
diff --git a/tests/v1/faq-inquiries/test_faq_inquiries.py b/tests/v1/faq-inquiries/test_faq_inquiries.py
new file mode 100644
index 000000000..91801f88d
--- /dev/null
+++ b/tests/v1/faq-inquiries/test_faq_inquiries.py
@@ -0,0 +1,65 @@
+from datetime import datetime, timezone
+from unittest.mock import MagicMock, patch
+
+import pytest
+from fastapi.testclient import TestClient
+from sqlalchemy.orm import Session
+from uuid_extensions import uuid7
+
+from api.db.database import get_db
+from api.utils.send_mail import send_faq_inquiry_mail
+from api.v1.models.faq_inquiries import FAQInquiries
+from main import app
+
+
+
+@pytest.fixture
+def db_session_mock():
+ db_session = MagicMock(spec=Session)
+ return db_session
+
+@pytest.fixture
+def client(db_session_mock):
+ app.dependency_overrides[get_db] = lambda: db_session_mock
+ client = TestClient(app)
+ yield client
+ app.dependency_overrides = {}
+
+
+def mock_post_inquiry():
+ return FAQInquiries(
+ id=str(uuid7()),
+ full_name="John Doe",
+ email="john.doe@gmail.com",
+ message="I have a question about the product.",
+ )
+
+
+@patch('fastapi.BackgroundTasks.add_task')
+@patch("api.v1.services.faq_inquiries.faq_inquiries_service.create")
+def test_submit_faq_inquiries(mock_post_inquiry_form, mock_add_task, db_session_mock, client):
+ """Tests the POST /api/v1/newsletter-subscription endpoint to ensure successful subscription with valid input."""
+
+ mock_post_inquiry_form.return_value = mock_post_inquiry()
+
+ db_session_mock.add.return_value = None
+ db_session_mock.commit.return_value = None
+ db_session_mock.refresh.return_value = None
+
+ response = client.post('/api/v1/faq-inquiries', json={
+ "full_name": "John Doe",
+ "email": "johndoe@gmail.com",
+ "message": "I have a question about the product."
+ })
+
+ assert response.status_code == 201
+
+ mock_add_task.assert_called_once()
+ mock_add_task.assert_called_with(
+ send_faq_inquiry_mail,
+ context={
+ "full_name": "John Doe",
+ "email": "john.doe@gmail.com",
+ "message": "I have a question about the product.",
+ }
+ )