Files
bakery-ia/services/auth/app/core/database.py

290 lines
10 KiB
Python
Raw Permalink Normal View History

2025-07-17 21:25:27 +02:00
# ================================================================
# services/auth/app/core/database.py (ENHANCED VERSION)
# ================================================================
"""
2025-07-17 21:25:27 +02:00
Database configuration for authentication service
"""
2025-07-18 14:41:39 +02:00
import structlog
2025-07-17 21:25:27 +02:00
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.pool import NullPool
from app.core.config import settings
2025-07-17 21:25:27 +02:00
from shared.database.base import Base
2025-07-18 14:41:39 +02:00
logger = structlog.get_logger()
2025-07-17 21:25:27 +02:00
# Create async engine
engine = create_async_engine(
settings.DATABASE_URL,
poolclass=NullPool,
echo=settings.DEBUG,
future=True
)
# Create session factory
AsyncSessionLocal = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False,
autoflush=False,
autocommit=False
)
2025-07-17 21:25:27 +02:00
async def get_db() -> AsyncSession:
"""Database dependency"""
async with AsyncSessionLocal() as session:
try:
yield session
await session.commit()
except Exception as e:
await session.rollback()
logger.error(f"Database session error: {e}")
raise
finally:
await session.close()
2025-07-17 21:25:27 +02:00
async def create_tables():
"""Create database tables"""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logger.info("Database tables created successfully")
2025-08-03 14:42:33 +02:00
# ================================================================
# services/auth/app/core/database.py - UPDATED TO USE SHARED INFRASTRUCTURE
# ================================================================
"""
Database configuration for authentication service
Uses shared database infrastructure for consistency
"""
import structlog
from typing import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from shared.database.base import DatabaseManager, Base
from app.core.config import settings
logger = structlog.get_logger()
# ✅ Initialize database manager using shared infrastructure
database_manager = DatabaseManager(settings.DATABASE_URL)
# ✅ Alias for convenience - matches the existing interface
get_db = database_manager.get_db
# ✅ Use the shared background session method
get_background_db_session = database_manager.get_background_session
async def get_db_health() -> bool:
"""
Health check function for database connectivity
"""
try:
async with database_manager.async_engine.begin() as conn:
await conn.execute(text("SELECT 1"))
logger.debug("Database health check passed")
return True
except Exception as e:
2026-01-15 20:45:49 +01:00
logger.error(f"Database health check failed: {str(e)}")
2025-08-03 14:42:33 +02:00
return False
async def create_tables():
"""Create database tables using shared infrastructure"""
await database_manager.create_tables()
logger.info("Auth database tables created successfully")
# ✅ Auth service specific database utilities
class AuthDatabaseUtils:
"""Auth service specific database utilities"""
@staticmethod
async def cleanup_old_refresh_tokens(days_old: int = 30):
"""Clean up old refresh tokens"""
try:
async with database_manager.get_background_session() as session:
if settings.DATABASE_URL.startswith("sqlite"):
query = text(
"DELETE FROM refresh_tokens "
"WHERE created_at < datetime('now', :days_param)"
)
params = {"days_param": f"-{days_old} days"}
else:
# PostgreSQL
query = text(
"DELETE FROM refresh_tokens "
"WHERE created_at < NOW() - INTERVAL :days_param"
)
params = {"days_param": f"{days_old} days"}
result = await session.execute(query, params)
# No need to commit - get_background_session() handles it
logger.info("Cleaned up old refresh tokens",
deleted_count=result.rowcount,
days_old=days_old)
return result.rowcount
except Exception as e:
logger.error("Failed to cleanup old refresh tokens",
error=str(e))
return 0
@staticmethod
async def get_auth_statistics(tenant_id: str = None) -> dict:
"""Get authentication statistics"""
try:
async with database_manager.get_background_session() as session:
# Base query for users
users_query = text("SELECT COUNT(*) as count FROM users WHERE is_active = :is_active")
params = {}
if tenant_id:
# If tenant filtering is needed (though auth service might not have tenant_id in users table)
# This is just an example - adjust based on your actual schema
pass
# Get active users count
active_users_result = await session.execute(
users_query,
{**params, "is_active": True}
)
active_users = active_users_result.scalar() or 0
# Get inactive users count
inactive_users_result = await session.execute(
users_query,
{**params, "is_active": False}
)
inactive_users = inactive_users_result.scalar() or 0
# Get refresh tokens count
tokens_query = text("SELECT COUNT(*) as count FROM refresh_tokens")
tokens_result = await session.execute(tokens_query)
active_tokens = tokens_result.scalar() or 0
return {
"active_users": active_users,
"inactive_users": inactive_users,
"total_users": active_users + inactive_users,
"active_tokens": active_tokens
}
except Exception as e:
2026-01-15 20:45:49 +01:00
logger.error(f"Failed to get auth statistics: {str(e)}")
2025-08-03 14:42:33 +02:00
return {
"active_users": 0,
"inactive_users": 0,
"total_users": 0,
"active_tokens": 0
}
@staticmethod
async def check_user_exists(user_id: str) -> bool:
"""Check if user exists"""
try:
async with database_manager.get_background_session() as session:
query = text(
"SELECT COUNT(*) as count "
"FROM users "
"WHERE id = :user_id "
"LIMIT 1"
)
result = await session.execute(query, {"user_id": user_id})
count = result.scalar() or 0
return count > 0
except Exception as e:
logger.error("Failed to check user existence",
user_id=user_id, error=str(e))
return False
@staticmethod
async def get_user_token_count(user_id: str) -> int:
"""Get count of active refresh tokens for a user"""
try:
async with database_manager.get_background_session() as session:
query = text(
"SELECT COUNT(*) as count "
"FROM refresh_tokens "
"WHERE user_id = :user_id"
)
result = await session.execute(query, {"user_id": user_id})
count = result.scalar() or 0
return count
except Exception as e:
logger.error("Failed to get user token count",
user_id=user_id, error=str(e))
return 0
# Enhanced database session dependency with better error handling
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
"""
Enhanced database session dependency with better logging and error handling
"""
async with database_manager.async_session_local() as session:
try:
logger.debug("Database session created")
yield session
except Exception as e:
2026-01-15 20:45:49 +01:00
logger.error(f"Database session error: {str(e)}", exc_info=True)
2025-08-03 14:42:33 +02:00
await session.rollback()
raise
finally:
await session.close()
logger.debug("Database session closed")
# Database initialization for auth service
async def initialize_auth_database():
"""Initialize database tables for auth service"""
try:
logger.info("Initializing auth service database")
# Import models to ensure they're registered
from app.models.users import User
from app.models.refresh_tokens import RefreshToken
# Create tables using shared infrastructure
await database_manager.create_tables()
logger.info("Auth service database initialized successfully")
except Exception as e:
2026-01-15 20:45:49 +01:00
logger.error(f"Failed to initialize auth service database: {str(e)}")
2025-08-03 14:42:33 +02:00
raise
# Database cleanup for auth service
async def cleanup_auth_database():
"""Cleanup database connections for auth service"""
try:
logger.info("Cleaning up auth service database connections")
# Close engine connections
if hasattr(database_manager, 'async_engine') and database_manager.async_engine:
await database_manager.async_engine.dispose()
logger.info("Auth service database cleanup completed")
except Exception as e:
2026-01-15 20:45:49 +01:00
logger.error(f"Failed to cleanup auth service database: {str(e)}")
2025-08-03 14:42:33 +02:00
# Export the commonly used items to maintain compatibility
__all__ = [
'Base',
'database_manager',
'get_db',
'get_background_db_session',
'get_db_session',
'get_db_health',
'AuthDatabaseUtils',
'initialize_auth_database',
'cleanup_auth_database',
'create_tables'
]