diff --git a/app/routes/contact.py b/app/routes/contact.py new file mode 100644 index 0000000..bff73a4 --- /dev/null +++ b/app/routes/contact.py @@ -0,0 +1,59 @@ +from fastapi import APIRouter, Request, Depends, HTTPException +from fastapi.responses import HTMLResponse, JSONResponse +from fastapi.templating import Jinja2Templates +from app.schemas import ContactCreate +from app.crud import create_contact +from app.services.email import send_contact_email +from app.config import get_settings +import httpx + +router = APIRouter() +templates = Jinja2Templates(directory="app/templates") + +@router.get("/contact", response_class=HTMLResponse) +async def contact_page(request: Request): + settings = get_settings() + return templates.TemplateResponse("contact.html", { + "request": request, + "recaptcha_site_key": settings.recaptcha_site_key + }) + +@router.post("/contact") +async def submit_contact(contact_data: ContactCreate): + # Verify reCAPTCHA + settings = get_settings() + recaptcha_secret = settings.recaptcha_secret + + async with httpx.AsyncClient() as client: + resp = await client.post( + "https://www.google.com/recaptcha/api/siteverify", + data={ + "secret": recaptcha_secret, + "response": contact_data.recaptcha_token + } + ) + result = resp.json() + if not result.get("success"): + raise HTTPException(status_code=400, detail="Vérification reCAPTCHA échouée") + + # Save to database + from app.database import get_db + db_gen = get_db() + db = await anext(db_gen) + try: + await create_contact(db, contact_data) + finally: + await db.close() + + # Send email (async, ignore errors) + try: + await send_contact_email( + name=contact_data.name, + email=contact_data.email, + subject=contact_data.subject, + message=contact_data.message + ) + except Exception as e: + print(f"Email send failed: {e}") + + return JSONResponse({"success": True, "message": "Message envoyé avec succès"}) diff --git a/app/templates/contact.html b/app/templates/contact.html new file mode 100644 index 0000000..992e455 --- /dev/null +++ b/app/templates/contact.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block title %}Geekbrain.io - Contact{% endblock %} + +{% block content %} +

Contactez-moi

+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+

Coordonnées

+

Email: rcairbum@gmail.com

+

Disponible pour missions freelance.

+
+
+{% endblock %} diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..52f2acb --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..5691727 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + web: + build: + context: .. + dockerfile: docker/Dockerfile + ports: + - "8000:8000" + environment: + DATABASE_URL: ${DATABASE_URL} + SMTP_HOST: ${SMTP_HOST} + SMTP_PORT: ${SMTP_PORT} + SMTP_USER: ${SMTP_USER} + SMTP_PASSWORD: ${SMTP_PASSWORD} + RECAPTCHA_SECRET: ${RECAPTCHA_SECRET} + RECAPTCHA_SITE_KEY: ${RECAPTCHA_SITE_KEY} + GITBUCKET_URL: ${GITBUCKET_URL} + CACHE_TTL: ${CACHE_TTL:-300} + LOG_LEVEL: ${LOG_LEVEL:-INFO} + volumes: + - ../app/static:/app/static + restart: unless-stopped diff --git a/tests/test_routes/test_contact.py b/tests/test_routes/test_contact.py new file mode 100644 index 0000000..5a60f3f --- /dev/null +++ b/tests/test_routes/test_contact.py @@ -0,0 +1,42 @@ +from fastapi.testclient import TestClient +from app.main import app +from unittest.mock import patch, AsyncMock, MagicMock + +client = TestClient(app) + +def test_contact_page_loads(): + response = client.get("/contact") + assert response.status_code == 200 + assert "Contact" in response.text + assert "g-recaptcha" in response.text + +@patch("app.routes.contact.create_contact", new_callable=AsyncMock) +@patch("app.routes.contact.send_contact_email", new_callable=AsyncMock) +@patch("app.routes.contact.httpx.AsyncClient.post") +def test_contact_post_success(mock_recaptcha_post, mock_send, mock_create): + # Mock the response from reCAPTCHA verification + mock_response = MagicMock() + mock_response.json.return_value = {"success": True} + # AsyncClient.post returns (await) the response + mock_recaptcha_post.return_value = mock_response + + response = client.post("/contact", json={ + "name": "Test", + "email": "test@example.com", + "subject": "Hello", + "message": "World", + "recaptcha_token": "fake-token" + }) + assert response.status_code == 200 + json = response.json() + assert json["success"] is True + +def test_contact_post_invalid_data(): + response = client.post("/contact", json={ + "name": "", + "email": "bad-email", + "subject": "Test", + "message": "Hello", + "recaptcha_token": "fake-token" + }) + assert response.status_code == 422 # Validation error