diff --git a/backend/app/api/api.py b/backend/app/api/api.py index ce08c1e..e77cbba 100644 --- a/backend/app/api/api.py +++ b/backend/app/api/api.py @@ -1,5 +1,5 @@ from fastapi import APIRouter -from app.api.endpoints import auth, pprs, public, aircraft, airport, local_flights, departures, arrivals, circuits, journal, overflights, public_book, movements, drone_requests +from app.api.endpoints import auth, pprs, public, aircraft, airport, local_flights, departures, arrivals, circuits, journal, overflights, public_book, movements, drone_requests, contact_requests api_router = APIRouter() @@ -13,6 +13,7 @@ api_router.include_router(circuits.router, prefix="/circuits", tags=["circuits"] api_router.include_router(journal.router, prefix="/journal", tags=["journal"]) api_router.include_router(movements.router, prefix="/movements", tags=["movements"]) api_router.include_router(drone_requests.router, prefix="/drone-requests", tags=["drone_requests"]) +api_router.include_router(contact_requests.router, prefix="/contact-requests", tags=["contact_requests"]) api_router.include_router(public.router, prefix="/public", tags=["public"]) api_router.include_router(public_book.router, prefix="/public-book", tags=["public_booking"]) api_router.include_router(aircraft.router, prefix="/aircraft", tags=["aircraft"]) diff --git a/backend/app/api/endpoints/contact_requests.py b/backend/app/api/endpoints/contact_requests.py new file mode 100644 index 0000000..32d0c20 --- /dev/null +++ b/backend/app/api/endpoints/contact_requests.py @@ -0,0 +1,50 @@ +from datetime import datetime, timezone + +from fastapi import APIRouter, Request + +from app.core.email import email_service +from app.core.utils import get_client_ip +from app.schemas.contact_request import ContactRequestCreate, ContactRequestReceipt + +router = APIRouter() + +CONTACT_REQUEST_RECIPIENT = "tower@swansea-airport.wales" + + +@router.post("/public", response_model=ContactRequestReceipt) +async def create_public_contact_request( + contact_request: ContactRequestCreate, + request: Request, +): + submitted_at = datetime.now(timezone.utc) + client_ip = get_client_ip(request) + + print( + "Public contact request received " + f"at={submitted_at.isoformat()} " + f"type={contact_request.enquiry_type.value} " + f"name={contact_request.name!r} " + f"email={contact_request.email} " + f"source={contact_request.source_page or '-'} " + f"ip={client_ip}" + ) + + await email_service.send_email( + to_email=CONTACT_REQUEST_RECIPIENT, + subject=f"Website contact: {contact_request.subject}", + template_name="contact_request.html", + reply_to=f"{contact_request.name} <{contact_request.email}>", + template_vars={ + "submitted_at": submitted_at.strftime("%Y-%m-%d %H:%M UTC"), + "client_ip": client_ip, + "name": contact_request.name, + "email": contact_request.email, + "phone": contact_request.phone, + "enquiry_type": contact_request.enquiry_type.value, + "subject": contact_request.subject, + "message": contact_request.message, + "source_page": contact_request.source_page, + }, + ) + + return ContactRequestReceipt() diff --git a/backend/app/core/email.py b/backend/app/core/email.py index 5192ec0..3a225cc 100644 --- a/backend/app/core/email.py +++ b/backend/app/core/email.py @@ -19,7 +19,14 @@ class EmailService: template_dir = os.path.join(os.path.dirname(__file__), '..', 'templates') self.jinja_env = Environment(loader=FileSystemLoader(template_dir)) - async def send_email(self, to_email: str, subject: str, template_name: str, template_vars: dict): + async def send_email( + self, + to_email: str, + subject: str, + template_name: str, + template_vars: dict, + reply_to: str | None = None, + ): # Render the template template = self.jinja_env.get_template(template_name) html_content = template.render(**template_vars) @@ -29,6 +36,8 @@ class EmailService: msg['Subject'] = subject msg['From'] = f"{self.from_name} <{self.from_email}>" msg['To'] = to_email + if reply_to: + msg['Reply-To'] = reply_to # Attach HTML content html_part = MIMEText(html_content, 'html') @@ -45,4 +54,4 @@ class EmailService: # In production, use logging -email_service = EmailService() \ No newline at end of file +email_service = EmailService() diff --git a/backend/app/schemas/contact_request.py b/backend/app/schemas/contact_request.py new file mode 100644 index 0000000..f996622 --- /dev/null +++ b/backend/app/schemas/contact_request.py @@ -0,0 +1,40 @@ +from enum import Enum +from typing import Optional + +from pydantic import BaseModel, EmailStr, Field, validator + + +class ContactEnquiryType(str, Enum): + GENERAL = "general" + AVIATION_BUSINESS = "aviation_business" + PILOT = "pilot" + EVENTS = "events" + COMMUNITY = "community" + + +class ContactRequestCreate(BaseModel): + name: str = Field(..., max_length=128) + email: EmailStr + phone: Optional[str] = Field(None, max_length=32) + enquiry_type: ContactEnquiryType + subject: str = Field(..., max_length=160) + message: str = Field(..., min_length=1, max_length=4000) + source_page: Optional[str] = Field(None, max_length=256) + + @validator("name", "subject", "message") + def validate_required_text(cls, value): + value = value.strip() + if not value: + raise ValueError("Field is required") + return value + + @validator("phone", "source_page") + def strip_optional_text(cls, value): + if value is None: + return value + value = value.strip() + return value or None + + +class ContactRequestReceipt(BaseModel): + status: str = "received" diff --git a/backend/app/templates/contact_request.html b/backend/app/templates/contact_request.html new file mode 100644 index 0000000..2199ed7 --- /dev/null +++ b/backend/app/templates/contact_request.html @@ -0,0 +1,64 @@ + + +
+ +| + + | +