Improve the frontend and fix TODOs
This commit is contained in:
@@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, Path, Query
|
||||
from typing import List, Dict, Any
|
||||
from uuid import UUID
|
||||
|
||||
from app.schemas.tenants import TenantMemberResponse
|
||||
from app.schemas.tenants import TenantMemberResponse, AddMemberWithUserCreate
|
||||
from app.services.tenant_service import EnhancedTenantService
|
||||
from shared.auth.decorators import get_current_user_dep
|
||||
from shared.routing.route_builder import RouteBuilder
|
||||
@@ -29,6 +29,116 @@ def get_enhanced_tenant_service():
|
||||
logger.error("Failed to create enhanced tenant service", error=str(e))
|
||||
raise HTTPException(status_code=500, detail="Service initialization failed")
|
||||
|
||||
@router.post(route_builder.build_base_route("{tenant_id}/members/with-user", include_tenant_prefix=False), response_model=TenantMemberResponse)
|
||||
@track_endpoint_metrics("tenant_add_member_with_user_creation")
|
||||
async def add_team_member_with_user_creation(
|
||||
member_data: AddMemberWithUserCreate,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
||||
):
|
||||
"""
|
||||
Add a team member to tenant with optional user creation (pilot phase).
|
||||
|
||||
This endpoint supports two modes:
|
||||
1. Adding an existing user: Set user_id and create_user=False
|
||||
2. Creating a new user: Set create_user=True and provide email, full_name, password
|
||||
|
||||
In pilot phase, this allows owners to directly create users with passwords.
|
||||
In production, this will be replaced with an invitation-based flow.
|
||||
"""
|
||||
try:
|
||||
user_id_to_add = member_data.user_id
|
||||
|
||||
# If create_user is True, create the user first via auth service
|
||||
if member_data.create_user:
|
||||
logger.info(
|
||||
"Creating new user before adding to tenant",
|
||||
tenant_id=str(tenant_id),
|
||||
email=member_data.email,
|
||||
requested_by=current_user["user_id"]
|
||||
)
|
||||
|
||||
# Call auth service to create user
|
||||
from shared.clients.auth_client import AuthServiceClient
|
||||
from app.core.config import settings
|
||||
|
||||
auth_client = AuthServiceClient(settings)
|
||||
|
||||
# Map tenant role to user role
|
||||
# tenant roles: admin, member, viewer
|
||||
# user roles: admin, manager, user
|
||||
user_role_map = {
|
||||
"admin": "admin",
|
||||
"member": "manager",
|
||||
"viewer": "user"
|
||||
}
|
||||
user_role = user_role_map.get(member_data.role, "user")
|
||||
|
||||
try:
|
||||
user_create_data = {
|
||||
"email": member_data.email,
|
||||
"full_name": member_data.full_name,
|
||||
"password": member_data.password,
|
||||
"phone": member_data.phone,
|
||||
"role": user_role,
|
||||
"language": member_data.language or "es",
|
||||
"timezone": member_data.timezone or "Europe/Madrid"
|
||||
}
|
||||
|
||||
created_user = await auth_client.create_user_by_owner(user_create_data)
|
||||
user_id_to_add = created_user.get("id")
|
||||
|
||||
logger.info(
|
||||
"User created successfully",
|
||||
user_id=user_id_to_add,
|
||||
email=member_data.email,
|
||||
tenant_id=str(tenant_id)
|
||||
)
|
||||
|
||||
except Exception as auth_error:
|
||||
logger.error(
|
||||
"Failed to create user via auth service",
|
||||
error=str(auth_error),
|
||||
email=member_data.email
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to create user account: {str(auth_error)}"
|
||||
)
|
||||
|
||||
# Add the user (existing or newly created) to the tenant
|
||||
result = await tenant_service.add_team_member(
|
||||
str(tenant_id),
|
||||
user_id_to_add,
|
||||
member_data.role,
|
||||
current_user["user_id"]
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Team member added successfully",
|
||||
tenant_id=str(tenant_id),
|
||||
user_id=user_id_to_add,
|
||||
role=member_data.role,
|
||||
user_was_created=member_data.create_user
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Add team member with user creation failed",
|
||||
tenant_id=str(tenant_id),
|
||||
error=str(e)
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to add team member"
|
||||
)
|
||||
|
||||
|
||||
@router.post(route_builder.build_base_route("{tenant_id}/members", include_tenant_prefix=False), response_model=TenantMemberResponse)
|
||||
@track_endpoint_metrics("tenant_add_member")
|
||||
async def add_team_member(
|
||||
@@ -38,7 +148,7 @@ async def add_team_member(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
||||
):
|
||||
"""Add a team member to tenant with enhanced validation and role management"""
|
||||
"""Add an existing team member to tenant (legacy endpoint)"""
|
||||
|
||||
try:
|
||||
result = await tenant_service.add_team_member(
|
||||
|
||||
@@ -825,10 +825,53 @@ async def cancel_subscription(
|
||||
"""Cancel subscription for a tenant"""
|
||||
|
||||
try:
|
||||
# TODO: Add access control - verify user is owner/admin of tenant
|
||||
# In a real implementation, you would need to retrieve the subscription ID from the database
|
||||
# For now, this is a placeholder
|
||||
subscription_id = "sub_test" # This would come from the database
|
||||
# Verify user is owner/admin of tenant
|
||||
user_id = current_user.get('user_id')
|
||||
user_role = current_user.get('role', '').lower()
|
||||
|
||||
# Check if user is tenant owner or admin
|
||||
from app.services.tenant_service import EnhancedTenantService
|
||||
from shared.database.base import create_database_manager
|
||||
|
||||
tenant_service = EnhancedTenantService(create_database_manager())
|
||||
|
||||
# Verify tenant access and role
|
||||
async with tenant_service.database_manager.get_session() as session:
|
||||
await tenant_service._init_repositories(session)
|
||||
|
||||
# Get tenant member record
|
||||
member = await tenant_service.member_repo.get_member_by_user_and_tenant(
|
||||
str(user_id), str(tenant_id)
|
||||
)
|
||||
|
||||
if not member:
|
||||
logger.warning("User not member of tenant",
|
||||
user_id=user_id,
|
||||
tenant_id=str(tenant_id))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied: You are not a member of this tenant"
|
||||
)
|
||||
|
||||
if member.role not in ['owner', 'admin']:
|
||||
logger.warning("Insufficient permissions to cancel subscription",
|
||||
user_id=user_id,
|
||||
tenant_id=str(tenant_id),
|
||||
role=member.role)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied: Only owners and admins can cancel subscriptions"
|
||||
)
|
||||
|
||||
# Get subscription ID from database
|
||||
subscription = await tenant_service.subscription_repo.get_active_subscription(str(tenant_id))
|
||||
if not subscription or not subscription.stripe_subscription_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No active subscription found for this tenant"
|
||||
)
|
||||
|
||||
subscription_id = subscription.stripe_subscription_id
|
||||
|
||||
result = await payment_service.cancel_subscription(subscription_id)
|
||||
|
||||
@@ -856,10 +899,40 @@ async def get_invoices(
|
||||
"""Get invoices for a tenant"""
|
||||
|
||||
try:
|
||||
# TODO: Add access control - verify user has access to tenant
|
||||
# In a real implementation, you would need to retrieve the customer ID from the database
|
||||
# For now, this is a placeholder
|
||||
customer_id = "cus_test" # This would come from the database
|
||||
# Verify user has access to tenant
|
||||
user_id = current_user.get('user_id')
|
||||
|
||||
from app.services.tenant_service import EnhancedTenantService
|
||||
from shared.database.base import create_database_manager
|
||||
|
||||
tenant_service = EnhancedTenantService(create_database_manager())
|
||||
|
||||
async with tenant_service.database_manager.get_session() as session:
|
||||
await tenant_service._init_repositories(session)
|
||||
|
||||
# Verify user is member of tenant
|
||||
member = await tenant_service.member_repo.get_member_by_user_and_tenant(
|
||||
str(user_id), str(tenant_id)
|
||||
)
|
||||
|
||||
if not member:
|
||||
logger.warning("User not member of tenant",
|
||||
user_id=user_id,
|
||||
tenant_id=str(tenant_id))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Access denied: You do not have access to this tenant"
|
||||
)
|
||||
|
||||
# Get subscription with customer ID
|
||||
subscription = await tenant_service.subscription_repo.get_active_subscription(str(tenant_id))
|
||||
if not subscription or not subscription.stripe_customer_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="No active subscription found for this tenant"
|
||||
)
|
||||
|
||||
customer_id = subscription.stripe_customer_id
|
||||
|
||||
invoices = await payment_service.get_invoices(customer_id)
|
||||
|
||||
|
||||
@@ -127,31 +127,135 @@ class TenantMemberRepository(TenantBaseRepository):
|
||||
raise DatabaseError(f"Failed to get membership: {str(e)}")
|
||||
|
||||
async def get_tenant_members(
|
||||
self,
|
||||
tenant_id: str,
|
||||
self,
|
||||
tenant_id: str,
|
||||
active_only: bool = True,
|
||||
role: str = None
|
||||
role: str = None,
|
||||
include_user_info: bool = False
|
||||
) -> List[TenantMember]:
|
||||
"""Get all members of a tenant"""
|
||||
"""Get all members of a tenant with optional user info enrichment"""
|
||||
try:
|
||||
filters = {"tenant_id": tenant_id}
|
||||
|
||||
|
||||
if active_only:
|
||||
filters["is_active"] = True
|
||||
|
||||
|
||||
if role:
|
||||
filters["role"] = role
|
||||
|
||||
return await self.get_multi(
|
||||
|
||||
members = await self.get_multi(
|
||||
filters=filters,
|
||||
order_by="joined_at",
|
||||
order_desc=False
|
||||
)
|
||||
|
||||
# If include_user_info is True, enrich with user data from auth service
|
||||
if include_user_info and members:
|
||||
members = await self._enrich_members_with_user_info(members)
|
||||
|
||||
return members
|
||||
except Exception as e:
|
||||
logger.error("Failed to get tenant members",
|
||||
tenant_id=tenant_id,
|
||||
error=str(e))
|
||||
raise DatabaseError(f"Failed to get members: {str(e)}")
|
||||
|
||||
async def _enrich_members_with_user_info(self, members: List[TenantMember]) -> List[TenantMember]:
|
||||
"""Enrich member objects with user information from auth service using batch endpoint"""
|
||||
try:
|
||||
import httpx
|
||||
import os
|
||||
|
||||
if not members:
|
||||
return members
|
||||
|
||||
# Get unique user IDs
|
||||
user_ids = list(set([str(member.user_id) for member in members]))
|
||||
|
||||
if not user_ids:
|
||||
return members
|
||||
|
||||
# Fetch user data from auth service using batch endpoint
|
||||
# Using internal service communication
|
||||
auth_service_url = os.getenv('AUTH_SERVICE_URL', 'http://auth-service:8000')
|
||||
|
||||
user_data_map = {}
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
# Use batch endpoint for efficiency
|
||||
response = await client.post(
|
||||
f"{auth_service_url}/api/v1/auth/users/batch",
|
||||
json={"user_ids": user_ids},
|
||||
timeout=10.0,
|
||||
headers={"X-Internal-Service": "tenant-service"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
batch_result = response.json()
|
||||
user_data_map = batch_result.get("users", {})
|
||||
logger.info(
|
||||
"Batch user fetch successful",
|
||||
requested_count=len(user_ids),
|
||||
found_count=batch_result.get("found_count", 0)
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
"Batch user fetch failed, falling back to individual calls",
|
||||
status_code=response.status_code
|
||||
)
|
||||
# Fallback to individual calls if batch fails
|
||||
for user_id in user_ids:
|
||||
try:
|
||||
response = await client.get(
|
||||
f"{auth_service_url}/api/v1/auth/users/{user_id}",
|
||||
timeout=5.0,
|
||||
headers={"X-Internal-Service": "tenant-service"}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
user_data = response.json()
|
||||
user_data_map[user_id] = user_data
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch user data for {user_id}", error=str(e))
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Batch user fetch failed, falling back to individual calls", error=str(e))
|
||||
# Fallback to individual calls
|
||||
for user_id in user_ids:
|
||||
try:
|
||||
response = await client.get(
|
||||
f"{auth_service_url}/api/v1/auth/users/{user_id}",
|
||||
timeout=5.0,
|
||||
headers={"X-Internal-Service": "tenant-service"}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
user_data = response.json()
|
||||
user_data_map[user_id] = user_data
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch user data for {user_id}", error=str(e))
|
||||
continue
|
||||
|
||||
# Enrich members with user data
|
||||
for member in members:
|
||||
user_id_str = str(member.user_id)
|
||||
if user_id_str in user_data_map and user_data_map[user_id_str] is not None:
|
||||
user_data = user_data_map[user_id_str]
|
||||
# Add user fields as attributes to the member object
|
||||
member.user_email = user_data.get("email")
|
||||
member.user_full_name = user_data.get("full_name")
|
||||
member.user = user_data # Store full user object for compatibility
|
||||
else:
|
||||
# Set defaults for missing users
|
||||
member.user_email = None
|
||||
member.user_full_name = "Unknown User"
|
||||
member.user = None
|
||||
|
||||
return members
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Failed to enrich members with user info", error=str(e))
|
||||
# Return members without enrichment if it fails
|
||||
return members
|
||||
|
||||
async def get_user_memberships(
|
||||
self,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Tenant schemas - FIXED VERSION
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from pydantic import BaseModel, Field, field_validator, ValidationInfo
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
@@ -20,31 +20,34 @@ class BakeryRegistration(BaseModel):
|
||||
business_model: Optional[str] = Field(default="individual_bakery")
|
||||
coupon_code: Optional[str] = Field(None, max_length=50, description="Promotional coupon code")
|
||||
|
||||
@validator('phone')
|
||||
@field_validator('phone')
|
||||
@classmethod
|
||||
def validate_spanish_phone(cls, v):
|
||||
"""Validate Spanish phone number"""
|
||||
# Remove spaces and common separators
|
||||
phone = re.sub(r'[\s\-\(\)]', '', v)
|
||||
|
||||
|
||||
# Spanish mobile: +34 6/7/8/9 + 8 digits
|
||||
# Spanish landline: +34 9 + 8 digits
|
||||
patterns = [
|
||||
r'^(\+34|0034|34)?[6789]\d{8}$', # Mobile
|
||||
r'^(\+34|0034|34)?9\d{8}$', # Landline
|
||||
]
|
||||
|
||||
|
||||
if not any(re.match(pattern, phone) for pattern in patterns):
|
||||
raise ValueError('Invalid Spanish phone number')
|
||||
return v
|
||||
|
||||
@validator('business_type')
|
||||
|
||||
@field_validator('business_type')
|
||||
@classmethod
|
||||
def validate_business_type(cls, v):
|
||||
valid_types = ['bakery', 'coffee_shop', 'pastry_shop', 'restaurant']
|
||||
if v not in valid_types:
|
||||
raise ValueError(f'Business type must be one of: {valid_types}')
|
||||
return v
|
||||
|
||||
@validator('business_model')
|
||||
|
||||
@field_validator('business_model')
|
||||
@classmethod
|
||||
def validate_business_model(cls, v):
|
||||
if v is None:
|
||||
return v
|
||||
@@ -72,7 +75,8 @@ class TenantResponse(BaseModel):
|
||||
created_at: datetime
|
||||
|
||||
# ✅ FIX: Add custom validator to convert UUID to string
|
||||
@validator('id', 'owner_id', pre=True)
|
||||
@field_validator('id', 'owner_id', mode='before')
|
||||
@classmethod
|
||||
def convert_uuid_to_string(cls, v):
|
||||
"""Convert UUID objects to strings for JSON serialization"""
|
||||
if isinstance(v, UUID):
|
||||
@@ -89,21 +93,26 @@ class TenantAccessResponse(BaseModel):
|
||||
permissions: List[str]
|
||||
|
||||
class TenantMemberResponse(BaseModel):
|
||||
"""Tenant member response - FIXED VERSION"""
|
||||
"""Tenant member response - FIXED VERSION with enriched user data"""
|
||||
id: str
|
||||
user_id: str
|
||||
role: str
|
||||
is_active: bool
|
||||
joined_at: Optional[datetime]
|
||||
|
||||
# Enriched user fields (populated via service layer)
|
||||
user_email: Optional[str] = None
|
||||
user_full_name: Optional[str] = None
|
||||
user: Optional[Dict[str, Any]] = None # Full user object for compatibility
|
||||
|
||||
# ✅ FIX: Add custom validator to convert UUID to string
|
||||
@validator('id', 'user_id', pre=True)
|
||||
@field_validator('id', 'user_id', mode='before')
|
||||
@classmethod
|
||||
def convert_uuid_to_string(cls, v):
|
||||
"""Convert UUID objects to strings for JSON serialization"""
|
||||
if isinstance(v, UUID):
|
||||
return str(v)
|
||||
return v
|
||||
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@@ -135,6 +144,42 @@ class TenantMemberUpdate(BaseModel):
|
||||
role: Optional[str] = Field(None, pattern=r'^(owner|admin|member|viewer)$')
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
class AddMemberWithUserCreate(BaseModel):
|
||||
"""Schema for adding member with optional user creation (pilot phase)"""
|
||||
# For existing users
|
||||
user_id: Optional[str] = Field(None, description="ID of existing user to add")
|
||||
|
||||
# For new user creation
|
||||
create_user: bool = Field(False, description="Whether to create a new user")
|
||||
email: Optional[str] = Field(None, description="Email for new user (if create_user=True)")
|
||||
full_name: Optional[str] = Field(None, min_length=2, max_length=100, description="Full name for new user")
|
||||
password: Optional[str] = Field(None, min_length=8, max_length=128, description="Password for new user")
|
||||
phone: Optional[str] = Field(None, description="Phone number for new user")
|
||||
language: Optional[str] = Field("es", pattern="^(es|en|eu)$", description="Preferred language")
|
||||
timezone: Optional[str] = Field("Europe/Madrid", description="User timezone")
|
||||
|
||||
# Common fields
|
||||
role: str = Field(..., pattern=r'^(admin|member|viewer)$', description="Role in the tenant")
|
||||
|
||||
@field_validator('email', 'full_name', 'password')
|
||||
@classmethod
|
||||
def validate_user_creation_fields(cls, v, info: ValidationInfo):
|
||||
"""Validate that required fields are present when creating a user"""
|
||||
if info.data.get('create_user') and info.field_name in ['email', 'full_name', 'password']:
|
||||
if not v:
|
||||
raise ValueError(f"{info.field_name} is required when create_user is True")
|
||||
return v
|
||||
|
||||
@field_validator('user_id')
|
||||
@classmethod
|
||||
def validate_user_id_or_create(cls, v, info: ValidationInfo):
|
||||
"""Ensure either user_id or create_user is provided"""
|
||||
if not v and not info.data.get('create_user'):
|
||||
raise ValueError("Either user_id or create_user must be provided")
|
||||
if v and info.data.get('create_user'):
|
||||
raise ValueError("Cannot specify both user_id and create_user")
|
||||
return v
|
||||
|
||||
class TenantSubscriptionUpdate(BaseModel):
|
||||
"""Schema for updating tenant subscription"""
|
||||
plan: str = Field(..., pattern=r'^(basic|professional|enterprise)$')
|
||||
@@ -151,7 +196,8 @@ class TenantStatsResponse(BaseModel):
|
||||
subscription_plan: str
|
||||
subscription_status: str
|
||||
|
||||
@validator('tenant_id', pre=True)
|
||||
@field_validator('tenant_id', mode='before')
|
||||
@classmethod
|
||||
def convert_uuid_to_string(cls, v):
|
||||
"""Convert UUID objects to strings for JSON serialization"""
|
||||
if isinstance(v, UUID):
|
||||
|
||||
@@ -98,9 +98,11 @@ class SubscriptionLimitService:
|
||||
if subscription.max_locations == -1:
|
||||
return {"can_add": True, "reason": "Unlimited locations allowed"}
|
||||
|
||||
# Count current locations (this would need to be implemented based on your location model)
|
||||
# For now, we'll assume 1 location per tenant as default
|
||||
current_locations = 1 # TODO: Implement actual location count
|
||||
# Count current locations
|
||||
# Currently, each tenant has 1 location (their primary bakery location)
|
||||
# This is stored in tenant.address, tenant.city, tenant.postal_code
|
||||
# If multi-location support is added in the future, this would query a locations table
|
||||
current_locations = 1 # Each tenant has one primary location
|
||||
|
||||
can_add = current_locations < subscription.max_locations
|
||||
return {
|
||||
@@ -130,11 +132,10 @@ class SubscriptionLimitService:
|
||||
# Check if unlimited products (-1)
|
||||
if subscription.max_products == -1:
|
||||
return {"can_add": True, "reason": "Unlimited products allowed"}
|
||||
|
||||
# Count current products (this would need to be implemented based on your product model)
|
||||
# For now, we'll return a placeholder
|
||||
current_products = 0 # TODO: Implement actual product count
|
||||
|
||||
|
||||
# Count current products from inventory service
|
||||
current_products = await self._get_ingredient_count(tenant_id)
|
||||
|
||||
can_add = current_products < subscription.max_products
|
||||
return {
|
||||
"can_add": can_add,
|
||||
@@ -358,7 +359,7 @@ class SubscriptionLimitService:
|
||||
# Get current usage - Team & Organization
|
||||
members = await self.member_repo.get_tenant_members(tenant_id, active_only=True)
|
||||
current_users = len(members)
|
||||
current_locations = 1 # TODO: Implement actual location count from locations service
|
||||
current_locations = 1 # Each tenant has one primary location
|
||||
|
||||
# Get current usage - Products & Inventory
|
||||
current_products = await self._get_ingredient_count(tenant_id)
|
||||
|
||||
@@ -427,24 +427,24 @@ class EnhancedTenantService:
|
||||
)
|
||||
|
||||
async def get_team_members(
|
||||
self,
|
||||
tenant_id: str,
|
||||
self,
|
||||
tenant_id: str,
|
||||
user_id: str,
|
||||
active_only: bool = True
|
||||
) -> List[TenantMemberResponse]:
|
||||
"""Get all team members for a tenant"""
|
||||
|
||||
"""Get all team members for a tenant with enriched user information"""
|
||||
|
||||
try:
|
||||
async with self.database_manager.get_session() as session:
|
||||
# Initialize repositories with session
|
||||
await self._init_repositories(session)
|
||||
|
||||
|
||||
members = await self.member_repo.get_tenant_members(
|
||||
tenant_id, active_only=active_only
|
||||
tenant_id, active_only=active_only, include_user_info=True
|
||||
)
|
||||
|
||||
|
||||
return [TenantMemberResponse.from_orm(member) for member in members]
|
||||
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user