feat: Enhance email templates and add plain text support for improved deliverability

This commit is contained in:
Christian Krakau-Louis
2025-04-17 22:22:09 +02:00
parent 6753444daf
commit 828719b318
13 changed files with 663 additions and 269 deletions
+74 -6
View File
@@ -11,6 +11,11 @@ from starlette.middleware.authentication import AuthenticationMiddleware
from dotenv import load_dotenv
import logging
import contextlib
import time
import asyncio
import sqlalchemy
from sqlalchemy.exc import OperationalError, ProgrammingError
from sqlalchemy import text
from .db import engine, get_db
from . import models
@@ -52,10 +57,54 @@ app.add_middleware(
# Then add SessionMiddleware (last added = first executed)
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
# Initialize database on startup
@app.on_event("startup")
async def startup_db_client():
logger.info("Starting database initialization")
# Function to check database connection
async def check_db_connection(max_retries=10, initial_retry_delay=1):
"""
Check database connection with retries and exponential backoff
Args:
max_retries: Maximum number of retry attempts
initial_retry_delay: Initial delay before first retry in seconds
Returns:
bool: True if connection succeeded, False otherwise
"""
retry_delay = initial_retry_delay
for attempt in range(1, max_retries + 1):
try:
# Try a simple query to test the connection
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
logger.info(f"Database connection successful on attempt {attempt}")
return True
except (OperationalError, ProgrammingError) as e:
if "Connection refused" in str(e) or "Can't connect" in str(e):
logger.warning(f"Database connection attempt {attempt}/{max_retries} failed: {str(e)}")
if attempt < max_retries:
logger.info(f"Retrying in {retry_delay} seconds...")
await asyncio.sleep(retry_delay)
# Exponential backoff with a maximum delay of 10 seconds
retry_delay = min(retry_delay * 2, 10)
else:
logger.error(f"Failed to connect to database after {max_retries} attempts")
return False
else:
# For other database errors, log and continue
logger.error(f"Database error: {str(e)}")
return False
except Exception as e:
logger.error(f"Unexpected error connecting to database: {str(e)}")
return False
return False
# Initialize database with schema and tables
async def initialize_database():
"""
Initialize the database schema and seed data
"""
try:
# Import models to ensure they're registered with Base before initialization
from . import models
@@ -63,10 +112,29 @@ async def startup_db_client():
# Initialize database (applies migrations and seeds data)
init_db()
logger.info("Database initialized and migrated successfully")
return True
except Exception as e:
logger.error(f"Database initialization error: {str(e)}")
# We continue even if there was an error, as the application might still work with partial functionality
return False
# Initialize database on startup
@app.on_event("startup")
async def startup_db_client():
logger.info("Starting database initialization")
# First, ensure database server is available with polling
max_retries = 15 # Try up to 15 times (with exponential backoff, this could be ~2 minutes)
connected = await check_db_connection(max_retries=max_retries)
if connected:
# Once connected, initialize the database
success = await initialize_database()
if success:
logger.info("Database setup completed successfully")
else:
logger.warning("Database initialization completed with errors")
else:
logger.error("Failed to connect to database, application may not function correctly")
# Mount static files
app.mount("/static", StaticFiles(directory="app/static"), name="static")