feat: production launch — email audit, team invites, security headers, router fixes
Some checks failed
CI / ruff (push) Successful in 9s
CI / pytest (push) Failing after 47m32s
CI / validate (push) Successful in 23s
CI / dependency-scanning (push) Successful in 29s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped

- Fix loyalty & monitoring router bugs (_get_router → named routers)
- Implement team invitation email with send_template + seed templates (en/fr/de)
- Add SecurityHeadersMiddleware (nosniff, HSTS, referrer-policy, permissions-policy)
- Build email audit admin page: service, schemas, API, page route, menu, i18n, HTML, JS
- Clean stale TODO in platform-menu-config.js
- Add 67 tests (unit + integration) covering all new functionality

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 18:24:30 +01:00
parent 4ebd419987
commit ce822af883
25 changed files with 2485 additions and 19 deletions

View File

@@ -13,7 +13,7 @@ from fastapi import APIRouter, Depends
from app.api.deps import require_module_access
from app.modules.enums import FrontendType
from .admin_email_templates import admin_email_templates_router
from .admin_email_templates import admin_email_logs_router, admin_email_templates_router
from .admin_messages import admin_messages_router
from .admin_notifications import admin_notifications_router
@@ -25,3 +25,4 @@ router = APIRouter(
router.include_router(admin_messages_router, tags=["admin-messages"])
router.include_router(admin_notifications_router, tags=["admin-notifications"])
router.include_router(admin_email_templates_router, tags=["admin-email-templates"])
router.include_router(admin_email_logs_router, tags=["admin-email-logs"])

View File

@@ -19,11 +19,17 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api
from app.core.database import get_db
from app.modules.messaging.schemas.email import (
EmailLogDetail,
EmailLogListResponse,
EmailLogStatsResponse,
)
from app.modules.messaging.services.email_service import EmailService
from app.modules.messaging.services.email_template_service import EmailTemplateService
from app.modules.tenancy.schemas.auth import UserContext
admin_email_templates_router = APIRouter(prefix="/email-templates")
admin_email_logs_router = APIRouter(prefix="/email-logs")
logger = logging.getLogger(__name__)
@@ -317,6 +323,14 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
"expires_in_days": "7",
"platform_name": "Orion",
},
"team_invitation": {
"invited_by_name": "John Doe",
"store_name": "Acme Corp",
"role_name": "Manager",
"acceptance_link": "https://example.com/store/invitation/accept?token=abc123",
"expiry_days": "7",
"platform_name": "Orion",
},
"subscription_welcome": {
"store_name": "Acme Corp",
"tier_name": "Business",
@@ -353,3 +367,77 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
},
}
return samples.get(template_code, {"platform_name": "Orion"})
# =============================================================================
# EMAIL LOG (AUDIT) ENDPOINTS
# =============================================================================
@admin_email_logs_router.get("", response_model=EmailLogListResponse)
def list_email_logs(
page: int = 1,
per_page: int = 50,
search: str | None = None,
status: str | None = None,
template_code: str | None = None,
store_id: int | None = None,
date_from: str | None = None,
date_to: str | None = None,
current_user: UserContext = Depends(get_current_admin_api),
db: Session = Depends(get_db),
):
"""
Get paginated email logs with filters.
Supports filtering by recipient email search, status, template type,
store, and date range.
"""
filters = {}
if search:
filters["search"] = search
if status:
filters["status"] = status
if template_code:
filters["template_code"] = template_code
if store_id:
filters["store_id"] = store_id
if date_from:
filters["date_from"] = date_from
if date_to:
filters["date_to"] = date_to
skip = (page - 1) * per_page
service = EmailTemplateService(db)
items, total = service.get_email_logs(filters=filters, skip=skip, limit=per_page)
total_pages = (total + per_page - 1) // per_page
return EmailLogListResponse(
items=items,
total=total,
page=page,
per_page=per_page,
total_pages=total_pages,
)
@admin_email_logs_router.get("/stats", response_model=EmailLogStatsResponse)
def get_email_log_stats(
current_user: UserContext = Depends(get_current_admin_api),
db: Session = Depends(get_db),
):
"""Get email log statistics: counts by status and by template type."""
service = EmailTemplateService(db)
return service.get_email_log_stats()
@admin_email_logs_router.get("/{log_id}", response_model=EmailLogDetail)
def get_email_log_detail(
log_id: int,
current_user: UserContext = Depends(get_current_admin_api),
db: Session = Depends(get_db),
):
"""Get full detail for a single email log including body content for preview."""
service = EmailTemplateService(db)
return service.get_email_log_detail(log_id)