# ================================================================ # services/notification/app/main.py - COMPLETE IMPLEMENTATION # ================================================================ """ Notification Service Main Application Handles email and WhatsApp notifications with full integration """ import structlog from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from app.core.config import settings from app.core.database import init_db from app.api.notifications import router as notification_router from app.services.messaging import setup_messaging, cleanup_messaging from shared.monitoring import setup_logging, HealthChecker from shared.monitoring.metrics import setup_metrics_early # Setup logging first setup_logging("notification-service", settings.LOG_LEVEL) logger = structlog.get_logger() # Global variables for lifespan access metrics_collector = None health_checker = None # Create FastAPI app FIRST app = FastAPI( title="Bakery Notification Service", description="Email and WhatsApp notification service for bakery forecasting platform", version="1.0.0", docs_url="/docs", redoc_url="/redoc" ) # Setup metrics BEFORE any middleware and BEFORE lifespan metrics_collector = setup_metrics_early(app, "notification-service") @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan events - NO MIDDLEWARE ADDED HERE""" global health_checker # Startup logger.info("Starting Notification Service...") try: # Initialize database await init_db() logger.info("Database initialized") # Setup messaging await setup_messaging() logger.info("Messaging initialized") # Register custom metrics (metrics_collector already exists) metrics_collector.register_counter("notifications_sent_total", "Total notifications sent", labels=["type", "status"]) metrics_collector.register_counter("emails_sent_total", "Total emails sent", labels=["status"]) metrics_collector.register_counter("whatsapp_sent_total", "Total WhatsApp messages sent", labels=["status"]) metrics_collector.register_histogram("notification_processing_duration_seconds", "Time spent processing notifications") metrics_collector.register_gauge("notification_queue_size", "Current notification queue size") # Setup health checker health_checker = HealthChecker("notification-service") # Add database health check async def check_database(): try: from app.core.database import get_db from sqlalchemy import text async for db in get_db(): await db.execute(text("SELECT 1")) return True except Exception as e: return f"Database error: {e}" health_checker.add_check("database", check_database, timeout=5.0, critical=True) # Add email service health check async def check_email_service(): try: from app.services.email_service import EmailService email_service = EmailService() return await email_service.health_check() except Exception as e: return f"Email service error: {e}" health_checker.add_check("email_service", check_email_service, timeout=10.0, critical=True) # Add WhatsApp service health check async def check_whatsapp_service(): try: from app.services.whatsapp_service import WhatsAppService whatsapp_service = WhatsAppService() return await whatsapp_service.health_check() except Exception as e: return f"WhatsApp service error: {e}" health_checker.add_check("whatsapp_service", check_whatsapp_service, timeout=10.0, critical=False) # Add messaging health check def check_messaging(): try: # Check if messaging is properly initialized from app.services.messaging import notification_publisher return notification_publisher.connected if notification_publisher else False except Exception as e: return f"Messaging error: {e}" health_checker.add_check("messaging", check_messaging, timeout=3.0, critical=False) # Store health checker in app state app.state.health_checker = health_checker logger.info("Notification Service started successfully") except Exception as e: logger.error(f"Failed to start Notification Service: {e}") raise yield # Shutdown logger.info("Shutting down Notification Service...") try: await cleanup_messaging() logger.info("Messaging cleanup completed") except Exception as e: logger.error(f"Error during messaging cleanup: {e}") # Set lifespan AFTER metrics setup app.router.lifespan_context = lifespan # CORS middleware (added after metrics setup) app.add_middleware( CORSMiddleware, allow_origins=getattr(settings, 'CORS_ORIGINS', ["*"]), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Include routers app.include_router(notification_router, prefix="/api/v1", tags=["notifications"]) # Health check endpoint @app.get("/health") async def health_check(): """Comprehensive health check endpoint""" if health_checker: return await health_checker.check_health() else: return { "service": "notification-service", "status": "healthy", "version": "1.0.0" } # Metrics endpoint @app.get("/metrics") async def metrics(): """Prometheus metrics endpoint""" if metrics_collector: return metrics_collector.get_metrics() return {"metrics": "not_available"} # Exception handlers @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): """Global exception handler with metrics""" logger.error(f"Unhandled exception: {exc}", exc_info=True) # Record error metric if available if metrics_collector: metrics_collector.increment_counter("errors_total", labels={"type": "unhandled"}) return JSONResponse( status_code=500, content={"detail": "Internal server error"} ) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)