2026-01-10 21:43:31 +01:00
|
|
|
"""Fetches subscription data for JWT enrichment at login time"""
|
|
|
|
|
|
2026-01-16 15:19:34 +01:00
|
|
|
from typing import Dict, Any, Optional, List
|
2026-01-10 21:43:31 +01:00
|
|
|
import logging
|
|
|
|
|
from fastapi import HTTPException, status
|
|
|
|
|
|
2026-01-16 15:19:34 +01:00
|
|
|
from shared.clients.tenant_client import TenantServiceClient
|
|
|
|
|
from shared.config.base import BaseServiceSettings
|
|
|
|
|
|
2026-01-10 21:43:31 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SubscriptionFetcher:
|
2026-01-16 15:19:34 +01:00
|
|
|
def __init__(self, config: BaseServiceSettings):
|
|
|
|
|
"""
|
|
|
|
|
Initialize SubscriptionFetcher with service configuration
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
config: BaseServiceSettings containing service configuration
|
|
|
|
|
"""
|
|
|
|
|
self.tenant_client = TenantServiceClient(config)
|
|
|
|
|
logger.info("SubscriptionFetcher initialized with TenantServiceClient")
|
2026-01-10 21:43:31 +01:00
|
|
|
|
|
|
|
|
async def get_user_subscription_context(
|
|
|
|
|
self,
|
2026-01-16 15:19:34 +01:00
|
|
|
user_id: str
|
2026-01-10 21:43:31 +01:00
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""
|
2026-01-16 15:19:34 +01:00
|
|
|
Fetch user's tenant memberships and subscription data using shared tenant client.
|
2026-01-10 21:43:31 +01:00
|
|
|
Called ONCE at login, not per-request.
|
2026-01-16 15:19:34 +01:00
|
|
|
|
|
|
|
|
This method uses the shared TenantServiceClient instead of direct HTTP calls,
|
|
|
|
|
providing better error handling, circuit breaking, and consistency.
|
2026-01-10 21:43:31 +01:00
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
{
|
|
|
|
|
"tenant_id": "primary-tenant-uuid",
|
|
|
|
|
"tenant_role": "owner",
|
|
|
|
|
"subscription": {
|
|
|
|
|
"tier": "professional",
|
|
|
|
|
"status": "active",
|
|
|
|
|
"valid_until": "2025-02-15T00:00:00Z"
|
|
|
|
|
},
|
|
|
|
|
"tenant_access": [
|
|
|
|
|
{"id": "uuid", "role": "admin", "tier": "starter"}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
logger.debug("Fetching subscription data for user: %s", user_id)
|
|
|
|
|
|
2026-01-16 15:19:34 +01:00
|
|
|
# Get user's tenant memberships using shared tenant client
|
|
|
|
|
memberships = await self.tenant_client.get_user_memberships(user_id)
|
|
|
|
|
|
|
|
|
|
if not memberships:
|
|
|
|
|
logger.info(f"User {user_id} has no tenant memberships - returning default subscription context")
|
|
|
|
|
return {
|
|
|
|
|
"tenant_id": None,
|
|
|
|
|
"tenant_role": None,
|
|
|
|
|
"subscription": {
|
|
|
|
|
"tier": "starter",
|
|
|
|
|
"status": "active",
|
|
|
|
|
"valid_until": None
|
|
|
|
|
},
|
|
|
|
|
"tenant_access": []
|
2026-01-10 21:43:31 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 15:19:34 +01:00
|
|
|
# Get primary tenant (first one, or the one with highest role)
|
|
|
|
|
primary_membership = memberships[0]
|
|
|
|
|
for membership in memberships:
|
|
|
|
|
if membership.get("role") == "owner":
|
|
|
|
|
primary_membership = membership
|
|
|
|
|
break
|
2026-01-10 21:43:31 +01:00
|
|
|
|
2026-01-16 15:19:34 +01:00
|
|
|
primary_tenant_id = primary_membership["tenant_id"]
|
|
|
|
|
primary_role = primary_membership["role"]
|
2026-01-10 21:43:31 +01:00
|
|
|
|
2026-01-16 15:19:34 +01:00
|
|
|
# Get subscription for primary tenant using shared tenant client
|
|
|
|
|
subscription_data = await self.tenant_client.get_subscription_details(primary_tenant_id)
|
2026-01-10 21:43:31 +01:00
|
|
|
|
2026-01-16 15:19:34 +01:00
|
|
|
if not subscription_data:
|
|
|
|
|
logger.warning(f"No subscription data found for primary tenant {primary_tenant_id}")
|
|
|
|
|
# Return with basic info but no subscription
|
2026-01-10 21:43:31 +01:00
|
|
|
return {
|
|
|
|
|
"tenant_id": primary_tenant_id,
|
|
|
|
|
"tenant_role": primary_role,
|
2026-01-16 15:19:34 +01:00
|
|
|
"subscription": None,
|
|
|
|
|
"tenant_access": memberships
|
2026-01-10 21:43:31 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-16 15:19:34 +01:00
|
|
|
# Build tenant access list with subscription info
|
|
|
|
|
tenant_access = []
|
|
|
|
|
for membership in memberships:
|
|
|
|
|
tenant_id = membership["tenant_id"]
|
|
|
|
|
role = membership["role"]
|
|
|
|
|
|
|
|
|
|
# Get subscription for each tenant using shared tenant client
|
|
|
|
|
tenant_sub = await self.tenant_client.get_subscription_details(tenant_id)
|
|
|
|
|
|
|
|
|
|
tier = "starter" # default
|
|
|
|
|
if tenant_sub:
|
|
|
|
|
tier = tenant_sub.get("plan", "starter")
|
|
|
|
|
|
|
|
|
|
tenant_access.append({
|
|
|
|
|
"id": tenant_id,
|
|
|
|
|
"role": role,
|
|
|
|
|
"tier": tier
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"tenant_id": primary_tenant_id,
|
|
|
|
|
"tenant_role": primary_role,
|
|
|
|
|
"subscription": {
|
|
|
|
|
"tier": subscription_data.get("plan", "starter"),
|
|
|
|
|
"status": subscription_data.get("status", "active"),
|
|
|
|
|
"valid_until": subscription_data.get("valid_until", None)
|
|
|
|
|
},
|
|
|
|
|
"tenant_access": tenant_access
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-10 21:43:31 +01:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Error fetching subscription data: {str(e)}", exc_info=True)
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
|
detail=f"Error fetching subscription data: {str(e)}"
|
|
|
|
|
)
|