Files
blog.pyaqa.ru/app/presentation/web/error_handlers.py
Sergey Vanyushkin b1878e470f feat(ui): add error handling, flash messages and SEO optimization
- 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
2026-05-02 16:23:57 +03:00

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)