# services/tenant/app/main.py """ Tenant Service FastAPI application """ from fastapi import FastAPI from sqlalchemy import text from app.core.config import settings from app.core.database import database_manager from app.api import tenants, tenant_members, tenant_operations, webhooks, plans, subscription, tenant_settings, whatsapp_admin, usage_forecast, enterprise_upgrade, tenant_locations, tenant_hierarchy, internal_demo from shared.service_base import StandardFastAPIService class TenantService(StandardFastAPIService): """Tenant Service with standardized setup""" expected_migration_version = "00001" async def on_startup(self, app): """Custom startup logic including migration verification""" await self.verify_migrations() await super().on_startup(app) async def verify_migrations(self): """Verify database schema matches the latest migrations.""" try: async with self.database_manager.get_session() as session: # Check if alembic_version table exists result = await session.execute(text(""" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'alembic_version' ) """)) table_exists = result.scalar() if table_exists: # If table exists, check the version result = await session.execute(text("SELECT version_num FROM alembic_version")) version = result.scalar() # For now, just log the version instead of strict checking to avoid startup failures self.logger.info(f"Migration verification successful: {version}") else: # If table doesn't exist, migrations might not have run yet # This is OK - the migration job should create it self.logger.warning("alembic_version table does not exist yet - migrations may not have run") except Exception as e: self.logger.warning(f"Migration verification failed (this may be expected during initial setup): {e}") def __init__(self): # Define expected database tables for health checks tenant_expected_tables = ['tenants', 'tenant_members', 'subscriptions'] # Note: api_prefix is empty because RouteBuilder already includes /api/v1 super().__init__( service_name="tenant-service", app_name="Tenant Management Service", description="Multi-tenant bakery management service", version="1.0.0", log_level=settings.LOG_LEVEL, api_prefix="", database_manager=database_manager, expected_tables=tenant_expected_tables ) async def on_startup(self, app: FastAPI): """Custom startup logic for tenant service""" # Import models to ensure they're registered with SQLAlchemy from app.models.tenants import Tenant, TenantMember, Subscription from app.models.tenant_settings import TenantSettings self.logger.info("Tenant models imported successfully") # Initialize Redis from shared.redis_utils import initialize_redis, get_redis_client await initialize_redis(settings.REDIS_URL, db=settings.REDIS_DB, max_connections=20) redis_client = await get_redis_client() self.logger.info("Redis initialized successfully") # Start usage tracking scheduler from app.jobs.usage_tracking_scheduler import start_scheduler await start_scheduler(self.database_manager, redis_client, settings) self.logger.info("Usage tracking scheduler started") async def on_shutdown(self, app: FastAPI): """Custom shutdown logic for tenant service""" # Stop usage tracking scheduler from app.jobs.usage_tracking_scheduler import stop_scheduler await stop_scheduler() self.logger.info("Usage tracking scheduler stopped") # Close Redis connection from shared.redis_utils import close_redis await close_redis() self.logger.info("Redis connection closed") # Database cleanup is handled by the base class def get_service_features(self): """Return tenant-specific features""" return [ "multi_tenant_management", "subscription_management", "tenant_isolation", "webhook_notifications", "member_management" ] def setup_custom_endpoints(self): """Setup custom endpoints for tenant service""" @self.app.get("/metrics") async def metrics(): """Prometheus metrics endpoint""" if self.metrics_collector: return self.metrics_collector.get_metrics() return {"metrics": "not_available"} # Create service instance service = TenantService() # Create FastAPI app with standardized setup app = service.create_app( docs_url="/docs", redoc_url="/redoc" ) # Setup standard endpoints service.setup_standard_endpoints() # Setup custom endpoints service.setup_custom_endpoints() # Include routers service.add_router(plans.router, tags=["subscription-plans"]) # Public endpoint service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(subscription.router, tags=["subscription"]) service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(usage_forecast.router, tags=["usage-forecast"]) # Usage forecasting & predictive analytics service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning # Register settings router BEFORE tenants router to ensure proper route matching service.add_router(tenant_settings.router, prefix="/api/v1/tenants", tags=["tenant-settings"]) service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(whatsapp_admin.router, prefix="/api/v1", tags=["whatsapp-admin"]) # Admin WhatsApp management service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(tenants.router, tags=["tenants"]) service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(tenant_members.router, tags=["tenant-members"]) service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(tenant_operations.router, tags=["tenant-operations"]) service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(webhooks.router, tags=["webhooks"]) service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(enterprise_upgrade.router, tags=["enterprise"]) # Enterprise tier upgrade endpoints service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(tenant_locations.router, tags=["tenant-locations"]) # Tenant locations endpoints service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning service.add_router(tenant_hierarchy.router, tags=["tenant-hierarchy"]) # Tenant hierarchy endpoints service.add_router(internal_demo.router, tags=["internal-demo"]) # Internal demo data cloning if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)