- Add custom error pages (404, 403, 500) with user-friendly messages
- Add flash message system with signed cookies for security
- Add toast notifications with auto-dismiss and manual close
- Add comprehensive SEO meta tags (description, keywords, OG, Twitter)
- Add canonical URLs for SEO
- Update routes to use slug-based URLs (/posts/{slug} instead of /posts/{id})
- Add Open Graph and Twitter Card meta tags for social sharing
- Add favicon SVG
- Update all templates with proper meta tags and URLs
- Add error handlers registration in main.py
- Add flash middleware for request handling
- Install itsdangerous dependency
189 lines
5.2 KiB
Python
189 lines
5.2 KiB
Python
"""Error handlers and middleware for web UI.
|
|
|
|
This module provides custom error pages and flash message middleware
|
|
for the web interface.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import HTTPException, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from app.presentation.web.flash import FlashManager, get_flash_messages
|
|
|
|
templates = Jinja2Templates(directory="app/presentation/templates")
|
|
|
|
|
|
async def setup_flash_manager(request: Request) -> None:
|
|
"""Setup flash manager on request state.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
"""
|
|
request.state.flash_manager = FlashManager(request)
|
|
|
|
|
|
async def add_flash_to_response(request: Request, response: HTMLResponse) -> None:
|
|
"""Add flash cookie to response if needed.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
response: FastAPI response object.
|
|
"""
|
|
if hasattr(request.state, "flash_manager"):
|
|
request.state.flash_manager.set_cookie(response)
|
|
|
|
|
|
def get_template_context(request: Request) -> dict[str, Any]:
|
|
"""Get base template context with flash messages.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
|
|
Returns:
|
|
Template context dictionary.
|
|
"""
|
|
from app.presentation.web.deps import can_create_post, get_user_role
|
|
|
|
user = getattr(request.state, "user", None)
|
|
user_role = get_user_role(user)
|
|
|
|
return {
|
|
"request": request,
|
|
"user": user,
|
|
"user_role": user_role.value if user_role else None,
|
|
"can_create": can_create_post(user),
|
|
"flash_messages": get_flash_messages(request),
|
|
}
|
|
|
|
|
|
async def http_exception_handler(request: Request, exc: HTTPException) -> HTMLResponse:
|
|
"""Handle HTTP exceptions with custom error pages.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
exc: HTTPException instance.
|
|
|
|
Returns:
|
|
HTMLResponse with error page.
|
|
"""
|
|
# Handle redirects (307, 308)
|
|
if exc.status_code in (307, 308) and "Location" in exc.headers:
|
|
return RedirectResponse(url=exc.headers["Location"], status_code=exc.status_code)
|
|
|
|
error_pages = {
|
|
403: ("Access Denied", "You don't have permission to access this page."),
|
|
404: ("Page Not Found", "The page you're looking for doesn't exist."),
|
|
500: ("Server Error", "Something went wrong on our end. Please try again later."),
|
|
}
|
|
|
|
error_title, error_message = error_pages.get(
|
|
exc.status_code, ("Error", exc.detail or "An unexpected error occurred.")
|
|
)
|
|
|
|
context = get_template_context(request)
|
|
context.update(
|
|
{
|
|
"error_code": exc.status_code,
|
|
"error_title": error_title,
|
|
"error_message": error_message,
|
|
}
|
|
)
|
|
|
|
return templates.TemplateResponse(
|
|
"pages/error.html",
|
|
context,
|
|
status_code=exc.status_code,
|
|
)
|
|
|
|
|
|
async def not_found_handler(request: Request, exc: HTTPException) -> HTMLResponse:
|
|
"""Handle 404 Not Found errors.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
exc: HTTPException instance.
|
|
|
|
Returns:
|
|
HTMLResponse with 404 page.
|
|
"""
|
|
context = get_template_context(request)
|
|
context.update(
|
|
{
|
|
"error_code": 404,
|
|
"error_title": "Page Not Found",
|
|
"error_message": "The page you're looking for doesn't exist or has been moved.",
|
|
}
|
|
)
|
|
|
|
return templates.TemplateResponse(
|
|
"pages/error.html",
|
|
context,
|
|
status_code=404,
|
|
)
|
|
|
|
|
|
async def forbidden_handler(request: Request, exc: HTTPException) -> HTMLResponse:
|
|
"""Handle 403 Forbidden errors.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
exc: HTTPException instance.
|
|
|
|
Returns:
|
|
HTMLResponse with 403 page.
|
|
"""
|
|
context = get_template_context(request)
|
|
context.update(
|
|
{
|
|
"error_code": 403,
|
|
"error_title": "Access Denied",
|
|
"error_message": "You don't have permission to access this resource. Please sign in or contact an administrator.",
|
|
}
|
|
)
|
|
|
|
return templates.TemplateResponse(
|
|
"pages/error.html",
|
|
context,
|
|
status_code=403,
|
|
)
|
|
|
|
|
|
async def server_error_handler(request: Request, exc: Exception) -> HTMLResponse:
|
|
"""Handle 500 Internal Server Error.
|
|
|
|
Args:
|
|
request: FastAPI request object.
|
|
exc: Exception instance.
|
|
|
|
Returns:
|
|
HTMLResponse with 500 page.
|
|
"""
|
|
context = get_template_context(request)
|
|
context.update(
|
|
{
|
|
"error_code": 500,
|
|
"error_title": "Server Error",
|
|
"error_message": "Something went wrong on our end. Please try again later or contact support if the problem persists.",
|
|
}
|
|
)
|
|
|
|
return templates.TemplateResponse(
|
|
"pages/error.html",
|
|
context,
|
|
status_code=500,
|
|
)
|
|
|
|
|
|
def register_error_handlers(app) -> None:
|
|
"""Register error handlers with FastAPI app.
|
|
|
|
Args:
|
|
app: FastAPI application instance.
|
|
"""
|
|
app.add_exception_handler(404, not_found_handler)
|
|
app.add_exception_handler(403, forbidden_handler)
|
|
app.add_exception_handler(500, server_error_handler)
|
|
app.add_exception_handler(HTTPException, http_exception_handler)
|