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
+
+
+{% 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