diff --git a/services/recipes/app/core/database.py b/services/recipes/app/core/database.py index 5f95f553..3c37749f 100644 --- a/services/recipes/app/core/database.py +++ b/services/recipes/app/core/database.py @@ -3,75 +3,18 @@ Database configuration and session management for Recipe Service """ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker, Session -from sqlalchemy.pool import StaticPool -from contextlib import contextmanager -from typing import Generator - +from shared.database.base import DatabaseManager, create_database_manager from .config import settings - -# Create database engine -engine = create_engine( - settings.DATABASE_URL, - poolclass=StaticPool, - pool_pre_ping=True, - pool_recycle=300, - echo=settings.DEBUG, +# Create database manager using shared async infrastructure +db_manager = create_database_manager( + database_url=settings.DATABASE_URL, + service_name="recipes-service", + echo=settings.DEBUG ) -# Create session factory -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - - -def get_db() -> Generator[Session, None, None]: - """ - Dependency to get database session - """ - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@contextmanager -def get_db_context() -> Generator[Session, None, None]: - """ - Context manager for database session - """ - db = SessionLocal() - try: - yield db - db.commit() - except Exception: - db.rollback() - raise - finally: - db.close() - - -class DatabaseManager: - """Database management utilities""" - - @staticmethod - def create_all_tables(): - """Create all database tables""" - from shared.database.base import Base - Base.metadata.create_all(bind=engine) - - @staticmethod - def drop_all_tables(): - """Drop all database tables (for testing)""" - from shared.database.base import Base - Base.metadata.drop_all(bind=engine) - - @staticmethod - def get_session() -> Session: - """Get a new database session""" - return SessionLocal() - - -# Database manager instance -db_manager = DatabaseManager() \ No newline at end of file +# Dependency for FastAPI routes +async def get_db(): + """FastAPI dependency to get database session""" + async for session in db_manager.get_db(): + yield session \ No newline at end of file diff --git a/services/recipes/app/main.py b/services/recipes/app/main.py index 0c8f9612..49adbf98 100644 --- a/services/recipes/app/main.py +++ b/services/recipes/app/main.py @@ -15,6 +15,8 @@ from contextlib import asynccontextmanager from .core.config import settings from .core.database import db_manager from .api import recipes, production, ingredients +# Import models to register them with SQLAlchemy metadata +from .models import recipes as recipe_models # Configure logging @@ -33,7 +35,7 @@ async def lifespan(app: FastAPI): # Create database tables try: - db_manager.create_all_tables() + await db_manager.create_tables() logger.info("Database tables created successfully") except Exception as e: logger.error(f"Failed to create database tables: {e}") @@ -97,17 +99,14 @@ async def health_check(): """Health check endpoint""" try: # Test database connection - db = db_manager.get_session() - try: - db.execute("SELECT 1") - finally: - db.close() + health_result = await db_manager.health_check() return { "status": "healthy", "service": settings.SERVICE_NAME, "version": settings.SERVICE_VERSION, - "environment": settings.ENVIRONMENT + "environment": settings.ENVIRONMENT, + "database": health_result } except Exception as e: logger.error(f"Health check failed: {e}") diff --git a/services/sales/app/services/ai_onboarding_service.py b/services/sales/app/services/ai_onboarding_service.py index 76fd1a9c..eca97364 100644 --- a/services/sales/app/services/ai_onboarding_service.py +++ b/services/sales/app/services/ai_onboarding_service.py @@ -505,7 +505,11 @@ class AIOnboardingService: "description": modifications.get("description") or approval.get("notes", ""), # Optional fields "brand": modifications.get("brand") or approval.get("suggested_supplier"), - "is_active": True + "is_active": True, + # Explicitly set boolean fields to ensure they're not NULL + "requires_refrigeration": modifications.get("requires_refrigeration", approval.get("requires_refrigeration", False)), + "requires_freezing": modifications.get("requires_freezing", approval.get("requires_freezing", False)), + "is_perishable": modifications.get("is_perishable", approval.get("is_perishable", False)) } # Add optional numeric fields only if they exist diff --git a/services/suppliers/app/main.py b/services/suppliers/app/main.py index 89bf4b70..c07b9e3f 100644 --- a/services/suppliers/app/main.py +++ b/services/suppliers/app/main.py @@ -32,10 +32,6 @@ async def lifespan(app: FastAPI): await init_db() logger.info("Database initialized successfully") - # Setup metrics - setup_metrics_early(app, "suppliers-service") - logger.info("Metrics setup completed") - yield except Exception as e: @@ -72,6 +68,12 @@ app.add_middleware( allow_headers=["*"], ) +# Setup metrics +try: + setup_metrics_early(app, "suppliers-service") + logger.info("Metrics setup completed") +except Exception as e: + logger.error("Metrics setup failed", error=str(e)) # Setup authentication middleware (commented out - not implemented) # setup_auth_middleware(app) diff --git a/shared/database/base.py b/shared/database/base.py index 57d44de0..fac60352 100644 --- a/shared/database/base.py +++ b/shared/database/base.py @@ -154,11 +154,17 @@ class DatabaseManager: try: target_metadata = metadata or Base.metadata async with self.async_engine.begin() as conn: - await conn.run_sync(target_metadata.create_all) + await conn.run_sync(target_metadata.create_all, checkfirst=True) logger.info("Database tables created successfully", service=self.service_name) except Exception as e: - logger.error(f"Failed to create tables: {e}", service=self.service_name) - raise DatabaseError(f"Table creation failed: {str(e)}") + # Check if it's a "relation already exists" error which can be safely ignored + error_str = str(e).lower() + if "already exists" in error_str or "duplicate" in error_str: + logger.warning(f"Some database objects already exist - continuing: {e}", service=self.service_name) + logger.info("Database tables creation completed (some already existed)", service=self.service_name) + else: + logger.error(f"Failed to create tables: {e}", service=self.service_name) + raise DatabaseError(f"Table creation failed: {str(e)}") async def drop_tables(self, metadata=None): """Drop database tables"""