feat: multi-Oracle server support with runtime switching
Complete implementation of multi-server Oracle database support: Backend: - Multi-pool Oracle with lazy loading per server - Email-to-server cache for automatic server discovery - JWT tokens include server_id claim - /auth/check-identity and /auth/check-email endpoints - /auth/my-servers endpoint for listing user's accessible servers - Server switch with password re-authentication Frontend: - New ServerSelector component for header dropdown - Multi-step login flow (identity → server → password) - Server switching from header with password modal - Mobile drawer menu with server selection - Dark mode support for all new components - URL bookmark support with ?server= query param Scripts: - Unified start.sh replacing start-prod.sh/start-test.sh - Unified ssh-tunnel.sh with multi-server support - Updated status.sh for new architecture Tests: - E2E tests for multi-server and single-server login flows - Backend unit tests for all new endpoints - Oracle multi-pool integration tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,7 @@ logger = logging.getLogger(__name__)
|
||||
telegram_bot_task = None
|
||||
ocr_job_worker_running = False
|
||||
cleanup_task_running = False
|
||||
email_cache_running = False
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -68,8 +69,33 @@ cleanup_task_running = False
|
||||
async def init_oracle_pool():
|
||||
"""Initialize Oracle connection pool (shared by all modules)."""
|
||||
logger.info("[ORACLE] Initializing connection pool...")
|
||||
await oracle_pool.initialize()
|
||||
logger.info("[ORACLE] ✅ Pool initialized successfully")
|
||||
|
||||
# Get configured servers
|
||||
servers = settings.get_oracle_servers()
|
||||
|
||||
if servers:
|
||||
# Multi-server mode: register all servers for lazy pool creation
|
||||
logger.info(f"[ORACLE] Registering {len(servers)} servers for lazy pool creation:")
|
||||
for srv in servers:
|
||||
oracle_pool.register_server(
|
||||
server_id=srv.id,
|
||||
host=srv.host,
|
||||
port=srv.port,
|
||||
user=srv.user,
|
||||
password=srv.password,
|
||||
sid=srv.sid,
|
||||
service_name=srv.service_name,
|
||||
)
|
||||
logger.info(f"[ORACLE] - {srv.id}: {srv.name} @ {srv.host}:{srv.port}")
|
||||
|
||||
# Mark as initialized (pools will be created lazily on first connection)
|
||||
await oracle_pool.initialize()
|
||||
else:
|
||||
# Legacy single-server mode: initialize with env vars
|
||||
logger.info("[ORACLE] Using legacy single-server configuration")
|
||||
await oracle_pool.initialize()
|
||||
|
||||
logger.info("[ORACLE] ✅ Pool manager initialized successfully")
|
||||
|
||||
|
||||
async def init_reports_cache():
|
||||
@@ -188,6 +214,44 @@ async def init_cleanup_task():
|
||||
cleanup_task_running = False
|
||||
|
||||
|
||||
async def init_email_server_cache():
|
||||
"""Initialize the email-server cache for multi-Oracle auto-discovery (US-003).
|
||||
|
||||
Builds a cache mapping emails to server IDs by querying CONTAFIN_ORACLE.UTILIZATORI
|
||||
on each configured Oracle server. Starts auto-refresh every 15 minutes.
|
||||
"""
|
||||
global email_cache_running
|
||||
|
||||
# Only initialize if multi-server mode is configured
|
||||
servers = settings.get_oracle_servers()
|
||||
if not servers or len(servers) <= 1:
|
||||
logger.info("[EMAIL-CACHE] Single-server mode, skipping email cache initialization")
|
||||
return
|
||||
|
||||
logger.info("[EMAIL-CACHE] Initializing email-server cache...")
|
||||
try:
|
||||
from shared.auth.email_server_cache import (
|
||||
email_server_cache,
|
||||
build_email_cache,
|
||||
start_email_cache_refresh
|
||||
)
|
||||
|
||||
# Build initial cache
|
||||
await build_email_cache()
|
||||
|
||||
# Start auto-refresh
|
||||
await start_email_cache_refresh()
|
||||
email_cache_running = True
|
||||
|
||||
stats = email_server_cache.get_cache_stats()
|
||||
logger.info(f"[EMAIL-CACHE] ✅ Cache initialized: {stats['total_emails']} emails")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[EMAIL-CACHE] ⚠️ Cache init failed: {e}")
|
||||
logger.warning("[EMAIL-CACHE] Multi-server email lookup will not be available")
|
||||
email_cache_running = False
|
||||
|
||||
|
||||
async def run_telegram_bot():
|
||||
"""Run Telegram bot as background task."""
|
||||
logger.info("[TELEGRAM] Starting bot...")
|
||||
@@ -301,7 +365,10 @@ async def startup_event():
|
||||
# Step 4: Initialize cleanup task for expired failed receipts (US-008)
|
||||
await init_cleanup_task()
|
||||
|
||||
# Step 5: Start Telegram bot as background task
|
||||
# Step 5: Initialize email-server cache for multi-Oracle (US-003)
|
||||
await init_email_server_cache()
|
||||
|
||||
# Step 6: Start Telegram bot as background task
|
||||
if settings.telegram_bot_token:
|
||||
telegram_bot_task = asyncio.create_task(run_telegram_bot())
|
||||
logger.info("[STARTUP] ✅ Telegram bot task created")
|
||||
@@ -321,13 +388,24 @@ async def startup_event():
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""Application shutdown - Cleanup resources."""
|
||||
global telegram_bot_task, ocr_job_worker_running, cleanup_task_running
|
||||
global telegram_bot_task, ocr_job_worker_running, cleanup_task_running, email_cache_running
|
||||
|
||||
logger.info("=" * 80)
|
||||
logger.info("[SHUTDOWN] Stopping ROA2WEB Unified Backend...")
|
||||
logger.info("=" * 80)
|
||||
|
||||
try:
|
||||
# Stop email cache auto-refresh (US-003)
|
||||
if email_cache_running:
|
||||
logger.info("[SHUTDOWN] Stopping email cache auto-refresh...")
|
||||
try:
|
||||
from shared.auth.email_server_cache import stop_email_cache_refresh
|
||||
await stop_email_cache_refresh()
|
||||
email_cache_running = False
|
||||
logger.info("[SHUTDOWN] Email cache stopped")
|
||||
except Exception as e:
|
||||
logger.error(f"[SHUTDOWN] Email cache error: {e}")
|
||||
|
||||
# Stop cleanup task (US-008)
|
||||
if cleanup_task_running:
|
||||
logger.info("[SHUTDOWN] Stopping cleanup task...")
|
||||
@@ -402,7 +480,9 @@ app.add_middleware(
|
||||
AuthenticationMiddleware,
|
||||
excluded_paths=[
|
||||
"/", "/docs", "/health", "/redoc", "/openapi.json",
|
||||
"/api/auth/login", "/api/auth/refresh",
|
||||
"/api/auth/login", "/api/auth/refresh", "/api/auth/check-email",
|
||||
"/api/auth/check-identity", # US-013: Dual login support (email + username)
|
||||
"/api/system/auth-mode", # Public endpoint for login mode detection
|
||||
"/api/telegram/auth/verify-user",
|
||||
"/api/telegram/auth/verify-email",
|
||||
"/api/telegram/auth/login-with-email",
|
||||
|
||||
Reference in New Issue
Block a user