diff --git a/backend/alwrity_utils/router_manager.py b/backend/alwrity_utils/router_manager.py index e582767b..d4010398 100644 --- a/backend/alwrity_utils/router_manager.py +++ b/backend/alwrity_utils/router_manager.py @@ -16,15 +16,22 @@ class RouterManager: self.app = app self.included_routers = [] self.failed_routers = [] + self._included_router_names = set() def include_router_safely(self, router, router_name: str = None) -> bool: """Include a router safely with error handling.""" verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true" + router_name = router_name or getattr(router, 'prefix', 'unknown') + + if router_name in self._included_router_names: + if verbose: + logger.info(f"↩️ Router already included, skipping duplicate: {router_name}") + return True try: self.app.include_router(router) - router_name = router_name or getattr(router, 'prefix', 'unknown') self.included_routers.append(router_name) + self._included_router_names.add(router_name) if verbose: logger.info(f"✅ Router included successfully: {router_name}") return True @@ -40,19 +47,21 @@ class RouterManager: # Import os locally to avoid UnboundLocalError if it's shadowed import os verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true" + demo_mode = os.getenv("ALWRITY_DEMO_MODE", "false").lower() == "true" try: if verbose: - logger.info("Including core routers...") + logger.info(f"Including core routers (demo_mode={demo_mode})...") + + # Subscription router MUST always be included (including demo mode) so + # payment/preflight/subscription endpoints remain available. + from api.subscription import router as subscription_router + self.include_router_safely(subscription_router, "subscription") # Component logic router from api.component_logic import router as component_logic_router self.include_router_safely(component_logic_router, "component_logic") - # Subscription router - from api.subscription import router as subscription_router - self.include_router_safely(subscription_router, "subscription") - # Step 3 Research router (core onboarding functionality) from api.onboarding_utils.step3_routes import router as step3_research_router self.include_router_safely(step3_research_router, "step3_research") diff --git a/backend/app.py b/backend/app.py index 9fb04613..9275e242 100644 --- a/backend/app.py +++ b/backend/app.py @@ -289,7 +289,10 @@ if not PODCAST_ONLY_DEMO_MODE: router_manager.include_optional_routers() else: logger.info("PODCAST_ONLY_DEMO_MODE enabled: including only podcast and subscription feature routers.") - app.include_router(subscription_router) + router_manager.include_router_safely(subscription_router, "subscription") +# Safety net: keep subscription routes available even if core inclusion flow changes +# in special modes (e.g., demo mode). De-dup is handled by RouterManager. +router_manager.include_router_safely(subscription_router, "subscription") # Include assets serving router (must be mounted to serve generated images) app.include_router(assets_serving_router) diff --git a/backend/main.py b/backend/main.py index 7f5ee43f..049a6aeb 100644 --- a/backend/main.py +++ b/backend/main.py @@ -244,6 +244,9 @@ async def onboarding_status(): # Include routers using modular utilities router_manager.include_core_routers() +# Safety net: keep subscription routes available even if core inclusion flow changes +# in special modes (e.g., demo mode). De-dup is handled by RouterManager. +router_manager.include_router_safely(subscription_router, "subscription") router_manager.include_optional_routers() # SEO Dashboard endpoints