Compare commits

..

1 Commits

Author SHA1 Message Date
ي
af5a6e0ee3 Add podcast-only demo startup flag and CLI toggle 2026-03-30 06:56:57 +05:30
3 changed files with 61 additions and 88 deletions

View File

@@ -48,6 +48,9 @@ load_dotenv(backend_dir / '.env') # backend/.env
load_dotenv(project_root / '.env') # root .env (fallback) load_dotenv(project_root / '.env') # root .env (fallback)
load_dotenv() # CWD .env (fallback) load_dotenv() # CWD .env (fallback)
# Feature flags (read early so app wiring can rely on a single source of truth)
PODCAST_ONLY_DEMO_MODE = os.getenv("PODCAST_ONLY_DEMO_MODE", "false").lower() == "true"
# Set up clean logging for end users # Set up clean logging for end users
from logging_config import setup_clean_logging from logging_config import setup_clean_logging
setup_clean_logging() setup_clean_logging()
@@ -405,48 +408,62 @@ async def analyze_urls_ai_endpoint(request: AnalyzeURLsRequest, current_user: di
"""Run AI-powered SEO analysis on selected URLs.""" """Run AI-powered SEO analysis on selected URLs."""
return await analyze_urls_ai(request, current_user) return await analyze_urls_ai(request, current_user)
# Include platform analytics router # Centralized mode helpers for router wiring.
from routers.platform_analytics import router as platform_analytics_router # Keep all mode decisions in this section to avoid scattered env checks.
app.include_router(platform_analytics_router) def is_podcast_only_demo_mode() -> bool:
# Include Bing Analytics Storage router to expose storage-backed endpoints return PODCAST_ONLY_DEMO_MODE
from routers.bing_analytics_storage import router as bing_analytics_storage_router
app.include_router(bing_analytics_storage_router)
app.include_router(images_router)
app.include_router(image_studio_router)
app.include_router(product_marketing_router)
app.include_router(campaign_creator_router)
# Include content assets router
from api.content_assets.router import router as content_assets_router
app.include_router(content_assets_router)
# Include Podcast Maker router def should_include_non_podcast_routers() -> bool:
return not is_podcast_only_demo_mode()
# Include Podcast Maker router (available in all modes)
from api.podcast.router import router as podcast_router from api.podcast.router import router as podcast_router
app.include_router(podcast_router) app.include_router(podcast_router)
# Include YouTube Creator Studio router if should_include_non_podcast_routers():
from api.youtube.router import router as youtube_router # Include platform analytics router
app.include_router(youtube_router, prefix="/api") from routers.platform_analytics import router as platform_analytics_router
app.include_router(platform_analytics_router)
# Include research configuration router # Include Bing Analytics Storage router to expose storage-backed endpoints
app.include_router(research_config_router, prefix="/api/research", tags=["research"]) from routers.bing_analytics_storage import router as bing_analytics_storage_router
app.include_router(bing_analytics_storage_router)
app.include_router(images_router)
app.include_router(image_studio_router)
app.include_router(product_marketing_router)
app.include_router(campaign_creator_router)
# Include Research Engine router (standalone AI research module) # Include content assets router
from api.research.router import router as research_engine_router from api.content_assets.router import router as content_assets_router
app.include_router(research_engine_router, tags=["Research Engine"]) app.include_router(content_assets_router)
# Scheduler dashboard routes # Include YouTube Creator Studio router
from api.scheduler_dashboard import router as scheduler_dashboard_router from api.youtube.router import router as youtube_router
app.include_router(scheduler_dashboard_router) app.include_router(youtube_router, prefix="/api")
app.include_router(oauth_token_monitoring_router)
# Autonomous Agents API routes (Phase 3A) # Include research configuration router
from api.agents_api import router as agents_router app.include_router(research_config_router, prefix="/api/research", tags=["research"])
app.include_router(agents_router)
# Today workflow routes # Include Research Engine router (standalone AI research module)
from api.today_workflow import router as today_workflow_router from api.research.router import router as research_engine_router
app.include_router(today_workflow_router) app.include_router(research_engine_router, tags=["Research Engine"])
# Scheduler dashboard routes
from api.scheduler_dashboard import router as scheduler_dashboard_router
app.include_router(scheduler_dashboard_router)
app.include_router(oauth_token_monitoring_router)
# Autonomous Agents API routes (Phase 3A)
from api.agents_api import router as agents_router
app.include_router(agents_router)
# Today workflow routes
from api.today_workflow import router as today_workflow_router
app.include_router(today_workflow_router)
else:
logger.info("PODCAST_ONLY_DEMO_MODE is enabled: non-podcast routers are not registered.")
# Setup frontend serving using modular utilities # Setup frontend serving using modular utilities
frontend_serving.setup_frontend_serving() frontend_serving.setup_frontend_serving()

View File

@@ -8,7 +8,6 @@ IMPORTANT: This is a compatibility layer. For new code, use UserAPIKeyContext di
""" """
import os import os
import time
from fastapi import Request from fastapi import Request
from loguru import logger from loguru import logger
from typing import Callable from typing import Callable
@@ -21,62 +20,9 @@ class APIKeyInjectionMiddleware:
for the duration of each request. for the duration of each request.
""" """
# Shared across middleware instances (module currently instantiates per request)
_missing_keys_log_timestamps = {}
def __init__(self): def __init__(self):
self.original_keys = {} self.original_keys = {}
@staticmethod
def _should_skip_missing_key_warning(request: Request) -> bool:
"""
Optionally suppress missing-key warnings for non-AI/internal routes.
Controlled by API_KEY_INJECTION_SKIP_NON_AI_WARNINGS (default: true).
"""
skip_non_ai_warnings = os.getenv('API_KEY_INJECTION_SKIP_NON_AI_WARNINGS', 'true').lower() in ('1', 'true', 'yes')
if not skip_non_ai_warnings:
return False
path_lower = (request.url.path or '').lower()
return (
path_lower.startswith('/api/subscription/')
or path_lower.startswith('/api/onboarding/')
or path_lower.endswith('/status')
or path_lower.endswith('/health')
or path_lower == '/health'
or path_lower == '/status'
)
def _log_missing_keys_non_blocking(self, request: Request, user_id: str) -> None:
"""
Log missing API keys without interrupting request flow.
- Defaults to debug-level logging.
- Optional warn once-per-user-per-interval via env:
API_KEY_INJECTION_MISSING_KEYS_LOG_MODE=warn_once
API_KEY_INJECTION_MISSING_KEYS_LOG_INTERVAL_SECONDS=900
"""
try:
if self._should_skip_missing_key_warning(request):
logger.debug(f"[API Key Injection] Missing keys for user {user_id} on non-AI route; skipping warning")
return
log_mode = os.getenv('API_KEY_INJECTION_MISSING_KEYS_LOG_MODE', 'debug').lower()
if log_mode != 'warn_once':
logger.debug(f"No API keys found for user {user_id}")
return
interval_seconds = int(os.getenv('API_KEY_INJECTION_MISSING_KEYS_LOG_INTERVAL_SECONDS', '900'))
now = time.time()
last_logged_at = self._missing_keys_log_timestamps.get(user_id, 0)
if (now - last_logged_at) >= max(interval_seconds, 1):
logger.warning(f"No API keys found for user {user_id}")
self._missing_keys_log_timestamps[user_id] = now
else:
logger.debug(f"No API keys found for user {user_id} (warning suppressed by interval)")
except Exception as log_error:
# Logging should never block request processing
logger.debug(f"[API Key Injection] Failed to log missing keys state for user {user_id}: {log_error}")
async def __call__(self, request: Request, call_next: Callable): async def __call__(self, request: Request, call_next: Callable):
""" """
Inject user-specific API keys before processing request, Inject user-specific API keys before processing request,
@@ -122,7 +68,7 @@ class APIKeyInjectionMiddleware:
# Get user-specific API keys from database # Get user-specific API keys from database
with user_api_keys(user_id) as user_keys: with user_api_keys(user_id) as user_keys:
if not user_keys: if not user_keys:
self._log_missing_keys_non_blocking(request, user_id) logger.warning(f"No API keys found for user {user_id}")
return await call_next(request) return await call_next(request)
# Save original environment values # Save original environment values
@@ -174,3 +120,4 @@ async def api_key_injection_middleware(request: Request, call_next: Callable):
""" """
middleware = APIKeyInjectionMiddleware() middleware = APIKeyInjectionMiddleware()
return await middleware(request, call_next) return await middleware(request, call_next)

View File

@@ -298,6 +298,11 @@ def main():
parser.add_argument("--dev", action="store_true", help="Enable development mode (auto-reload)") parser.add_argument("--dev", action="store_true", help="Enable development mode (auto-reload)")
parser.add_argument("--production", action="store_true", help="Enable production mode (optimized for deployment)") parser.add_argument("--production", action="store_true", help="Enable production mode (optimized for deployment)")
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging for debugging") parser.add_argument("--verbose", action="store_true", help="Enable verbose logging for debugging")
parser.add_argument(
"--podcast-only-demo",
action="store_true",
help="Enable podcast-only demo mode (sets PODCAST_ONLY_DEMO_MODE=true before app startup)",
)
args = parser.parse_args() args = parser.parse_args()
# Determine mode # Determine mode
@@ -307,6 +312,8 @@ def main():
# Set global verbose flag for utilities # Set global verbose flag for utilities
os.environ["ALWRITY_VERBOSE"] = "true" if verbose_mode else "false" os.environ["ALWRITY_VERBOSE"] = "true" if verbose_mode else "false"
if args.podcast_only_demo:
os.environ["PODCAST_ONLY_DEMO_MODE"] = "true"
print("[*] ALwrity Backend Server") print("[*] ALwrity Backend Server")
print("=" * 40) print("=" * 40)
@@ -314,6 +321,8 @@ def main():
print(f"Auto-reload: {'ENABLED' if enable_reload else 'DISABLED'}") print(f"Auto-reload: {'ENABLED' if enable_reload else 'DISABLED'}")
if verbose_mode: if verbose_mode:
print("Verbose logging: ENABLED") print("Verbose logging: ENABLED")
if args.podcast_only_demo:
print("Podcast-only demo mode: ENABLED")
print("=" * 40) print("=" * 40)
# Check if we're in the right directory # Check if we're in the right directory