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:
Claude Agent
2026-01-26 22:39:06 +00:00
parent 5f99ee2fd0
commit b137e80b71
102 changed files with 9398 additions and 2787 deletions

View File

@@ -45,13 +45,17 @@ def create_companies_router(
)
# Helper function to get companies - can be cached
async def _get_user_companies_data(username: str) -> List[Company]:
async def _get_user_companies_data(username: str, server_id: Optional[str] = None) -> List[Company]:
"""
Get list of companies for a user from Oracle.
Args:
username: The username to get companies for
server_id: The Oracle server ID (for multi-server mode)
"""
companies = []
async with oracle_pool.get_connection() as connection:
async with oracle_pool.get_connection(server_id) as connection:
with connection.cursor() as cursor:
try:
# Get user ID
@@ -97,10 +101,11 @@ def create_companies_router(
return companies
# Apply cache decorator if provided
# Include server_id in cache key for multi-server mode
if cache_decorator:
_get_user_companies_data = cache_decorator(
cache_type='companies',
key_params=['username']
key_params=['username', 'server_id']
)(_get_user_companies_data)
@router.get("", response_model=CompanyListResponse)
@@ -111,7 +116,9 @@ def create_companies_router(
):
"""Get list of companies the user has access to."""
try:
companies = await _get_user_companies_data(current_user.username)
# Get server_id from request state (injected by auth middleware from JWT)
server_id = getattr(request.state, 'server_id', None)
companies = await _get_user_companies_data(current_user.username, server_id)
return CompanyListResponse(
companies=companies,
@@ -124,6 +131,7 @@ def create_companies_router(
@router.get("/{company_id}", response_model=Company)
async def get_company_details(
company_id: str,
request: Request,
current_user: CurrentUser = Depends(get_current_user)
):
"""Get details of a specific company."""
@@ -132,7 +140,9 @@ def create_companies_router(
raise HTTPException(403, f"Nu aveți acces la firma {company_id}")
try:
async with oracle_pool.get_connection() as connection:
# Get server_id from request state (injected by auth middleware from JWT)
server_id = getattr(request.state, 'server_id', None)
async with oracle_pool.get_connection(server_id) as connection:
with connection.cursor() as cursor:
cursor.execute("""
SELECT ID_FIRMA, FIRMA, SCHEMA, COD_FISCAL