feat: production launch — email audit, team invites, security headers, router fixes
- 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:
@@ -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"])
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user