Fix tenant register 2
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Tenant routes for gateway
|
Tenant routes for gateway - FIXED VERSION
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, HTTPException
|
from fastapi import APIRouter, Request, HTTPException
|
||||||
@@ -17,16 +17,37 @@ async def create_tenant(request: Request):
|
|||||||
"""Proxy tenant creation to tenant service"""
|
"""Proxy tenant creation to tenant service"""
|
||||||
try:
|
try:
|
||||||
body = await request.body()
|
body = await request.body()
|
||||||
auth_header = request.headers.get("Authorization")
|
|
||||||
|
# ✅ FIX: Forward all headers AND add user context from gateway auth
|
||||||
|
headers = dict(request.headers)
|
||||||
|
headers.pop("host", None) # Remove host header
|
||||||
|
|
||||||
|
# ✅ ADD USER CONTEXT FROM GATEWAY AUTHENTICATION
|
||||||
|
# Gateway middleware already verified the token and added user to request.state
|
||||||
|
if hasattr(request.state, 'user'):
|
||||||
|
headers["X-User-ID"] = str(request.state.user.get("user_id"))
|
||||||
|
headers["X-User-Email"] = request.state.user.get("email", "")
|
||||||
|
headers["X-User-Role"] = request.state.user.get("role", "user")
|
||||||
|
|
||||||
|
# Add tenant ID if it exists
|
||||||
|
if hasattr(request.state, 'tenant_id') and request.state.tenant_id:
|
||||||
|
headers["X-Tenant-ID"] = str(request.state.tenant_id)
|
||||||
|
elif request.state.user.get("tenant_id"):
|
||||||
|
headers["X-Tenant-ID"] = str(request.state.user.get("tenant_id"))
|
||||||
|
|
||||||
|
roles = request.state.user.get("roles", [])
|
||||||
|
if roles:
|
||||||
|
headers["X-User-Roles"] = ",".join(roles)
|
||||||
|
|
||||||
|
permissions = request.state.user.get("permissions", [])
|
||||||
|
if permissions:
|
||||||
|
headers["X-User-Permissions"] = ",".join(permissions)
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/register",
|
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/register",
|
||||||
content=body,
|
content=body,
|
||||||
headers={
|
headers=headers
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": auth_header
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
@@ -45,12 +66,81 @@ async def create_tenant(request: Request):
|
|||||||
async def get_tenants(request: Request):
|
async def get_tenants(request: Request):
|
||||||
"""Get tenants"""
|
"""Get tenants"""
|
||||||
try:
|
try:
|
||||||
auth_header = request.headers.get("Authorization")
|
# ✅ FIX: Same pattern for GET requests
|
||||||
|
headers = dict(request.headers)
|
||||||
|
headers.pop("host", None)
|
||||||
|
|
||||||
|
# Add user context from gateway auth
|
||||||
|
if hasattr(request.state, 'user'):
|
||||||
|
headers["X-User-ID"] = str(request.state.user.get("user_id"))
|
||||||
|
headers["X-User-Email"] = request.state.user.get("email", "")
|
||||||
|
headers["X-User-Role"] = request.state.user.get("role", "user")
|
||||||
|
|
||||||
|
if hasattr(request.state, 'tenant_id') and request.state.tenant_id:
|
||||||
|
headers["X-Tenant-ID"] = str(request.state.tenant_id)
|
||||||
|
elif request.state.user.get("tenant_id"):
|
||||||
|
headers["X-Tenant-ID"] = str(request.state.user.get("tenant_id"))
|
||||||
|
|
||||||
|
roles = request.state.user.get("roles", [])
|
||||||
|
if roles:
|
||||||
|
headers["X-User-Roles"] = ",".join(roles)
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||||
response = await client.get(
|
response = await client.get(
|
||||||
f"{settings.TENANT_SERVICE_URL}/tenants",
|
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants",
|
||||||
headers={"Authorization": auth_header}
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=response.status_code,
|
||||||
|
content=response.json()
|
||||||
|
)
|
||||||
|
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
logger.error(f"Tenant service unavailable: {e}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=503,
|
||||||
|
detail="Tenant service unavailable"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ✅ ADD: Generic proxy function like the data service has
|
||||||
|
async def _proxy_tenant_request(request: Request, target_path: str, method: str = None):
|
||||||
|
"""Proxy request to tenant service with user context"""
|
||||||
|
try:
|
||||||
|
url = f"{settings.TENANT_SERVICE_URL}{target_path}"
|
||||||
|
|
||||||
|
# Forward headers with user context
|
||||||
|
headers = dict(request.headers)
|
||||||
|
headers.pop("host", None)
|
||||||
|
|
||||||
|
# Add user context from gateway authentication
|
||||||
|
if hasattr(request.state, 'user'):
|
||||||
|
headers["X-User-ID"] = str(request.state.user.get("user_id"))
|
||||||
|
headers["X-User-Email"] = request.state.user.get("email", "")
|
||||||
|
headers["X-User-Role"] = request.state.user.get("role", "user")
|
||||||
|
|
||||||
|
if hasattr(request.state, 'tenant_id') and request.state.tenant_id:
|
||||||
|
headers["X-Tenant-ID"] = str(request.state.tenant_id)
|
||||||
|
elif request.state.user.get("tenant_id"):
|
||||||
|
headers["X-Tenant-ID"] = str(request.state.user.get("tenant_id"))
|
||||||
|
|
||||||
|
roles = request.state.user.get("roles", [])
|
||||||
|
if roles:
|
||||||
|
headers["X-User-Roles"] = ",".join(roles)
|
||||||
|
|
||||||
|
# Get request body if present
|
||||||
|
body = None
|
||||||
|
request_method = method or request.method
|
||||||
|
if request_method in ["POST", "PUT", "PATCH"]:
|
||||||
|
body = await request.body()
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
response = await client.request(
|
||||||
|
method=request_method,
|
||||||
|
url=url,
|
||||||
|
headers=headers,
|
||||||
|
content=body,
|
||||||
|
params=dict(request.query_params)
|
||||||
)
|
)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# Complete Authentication Test with Registration
|
# Complete Authentication Test with Registration - FIXED VERSION
|
||||||
# Tests the full user lifecycle: registration → login → API access
|
# Tests the full user lifecycle: registration → login → API access
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
@@ -14,7 +14,8 @@ AUTH_BASE="$API_BASE/api/v1/auth"
|
|||||||
TEST_EMAIL="test-$(date +%s)@bakery.com" # Unique email for each test
|
TEST_EMAIL="test-$(date +%s)@bakery.com" # Unique email for each test
|
||||||
TEST_PASSWORD="SecurePass123!"
|
TEST_PASSWORD="SecurePass123!"
|
||||||
TEST_NAME="Test Baker"
|
TEST_NAME="Test Baker"
|
||||||
TENANT_ID="test-tenant-$(date +%s)"
|
# ✅ FIX: Generate a proper UUID for tenant testing (will be replaced after bakery creation)
|
||||||
|
TENANT_ID=$(uuidgen 2>/dev/null || python3 -c "import uuid; print(uuid.uuid4())" 2>/dev/null || echo "00000000-0000-0000-0000-000000000000")
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -73,6 +74,12 @@ if ! check_service_health "http://localhost:8001" "Auth Service"; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check Tenant Service
|
||||||
|
if ! check_service_health "http://localhost:8005" "Tenant Service"; then
|
||||||
|
log_error "Tenant Service is not running. Check: docker-compose logs tenant-service"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Check Data Service
|
# Check Data Service
|
||||||
if ! check_service_health "http://localhost:8004" "Data Service"; then
|
if ! check_service_health "http://localhost:8004" "Data Service"; then
|
||||||
log_warning "Data Service is not running, but continuing with auth tests..."
|
log_warning "Data Service is not running, but continuing with auth tests..."
|
||||||
@@ -149,13 +156,13 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# STEP 4: ACCESSING PROTECTED ENDPOINTS
|
# STEP 3: ACCESSING PROTECTED ENDPOINTS
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
log_step "Step 4: Testing protected endpoints with authentication"
|
log_step "Step 3: Testing protected endpoints with authentication"
|
||||||
|
|
||||||
# 4a. Get current user info
|
# 3a. Get current user info
|
||||||
log_step "4a. Getting current user profile"
|
log_step "3a. Getting current user profile"
|
||||||
|
|
||||||
USER_PROFILE_RESPONSE=$(curl -s -X GET "$API_BASE/api/v1/users/me" \
|
USER_PROFILE_RESPONSE=$(curl -s -X GET "$API_BASE/api/v1/users/me" \
|
||||||
-H "Authorization: Bearer $ACCESS_TOKEN")
|
-H "Authorization: Bearer $ACCESS_TOKEN")
|
||||||
@@ -171,53 +178,11 @@ fi
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# 4b. Test data service through gateway
|
|
||||||
log_step "4b. Testing data service through gateway"
|
|
||||||
|
|
||||||
DATA_RESPONSE=$(curl -s -X GET "$API_BASE/api/v1/data/sales" \
|
|
||||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
|
||||||
-H "X-Tenant-ID: $TENANT_ID")
|
|
||||||
|
|
||||||
echo "Data Service Response:"
|
|
||||||
echo "$DATA_RESPONSE" | jq '.'
|
|
||||||
|
|
||||||
if [ "$(echo "$DATA_RESPONSE" | jq -r '.status // "unknown"')" != "error" ]; then
|
|
||||||
log_success "Data service access successful!"
|
|
||||||
else
|
|
||||||
log_warning "Data service returned error (may be expected for new tenant)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 4c. Test training service through gateway
|
|
||||||
log_step "4c. Testing training service through gateway"
|
|
||||||
|
|
||||||
TRAINING_RESPONSE=$(curl -s -X POST "$API_BASE/api/v1/training/jobs" \
|
|
||||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
|
||||||
-H "X-Tenant-ID: $TENANT_ID" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"include_weather": true,
|
|
||||||
"include_traffic": false,
|
|
||||||
"min_data_points": 30
|
|
||||||
}')
|
|
||||||
|
|
||||||
echo "Training Service Response:"
|
|
||||||
echo "$TRAINING_RESPONSE" | jq '.'
|
|
||||||
|
|
||||||
if echo "$TRAINING_RESPONSE" | jq -e '.job_id // .message' > /dev/null; then
|
|
||||||
log_success "Training service access successful!"
|
|
||||||
else
|
|
||||||
log_warning "Training service access may have issues"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# STEP 5: TENANT REGISTRATION (OPTIONAL)
|
# STEP 4: TENANT REGISTRATION (BAKERY CREATION)
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
log_step "Step 5: Registering a bakery/tenant"
|
log_step "Step 4: Registering a bakery/tenant"
|
||||||
|
|
||||||
BAKERY_RESPONSE=$(curl -s -X POST "$API_BASE/api/v1/tenants/register" \
|
BAKERY_RESPONSE=$(curl -s -X POST "$API_BASE/api/v1/tenants/register" \
|
||||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||||
@@ -235,19 +200,80 @@ echo "Bakery Registration Response:"
|
|||||||
echo "$BAKERY_RESPONSE" | jq '.'
|
echo "$BAKERY_RESPONSE" | jq '.'
|
||||||
|
|
||||||
if echo "$BAKERY_RESPONSE" | jq -e '.id' > /dev/null; then
|
if echo "$BAKERY_RESPONSE" | jq -e '.id' > /dev/null; then
|
||||||
|
# ✅ FIX: Use the actual tenant ID returned from bakery creation
|
||||||
TENANT_ID=$(echo "$BAKERY_RESPONSE" | jq -r '.id')
|
TENANT_ID=$(echo "$BAKERY_RESPONSE" | jq -r '.id')
|
||||||
log_success "Bakery registration successful! Tenant ID: $TENANT_ID"
|
log_success "Bakery registration successful! Tenant ID: $TENANT_ID"
|
||||||
else
|
else
|
||||||
log_warning "Bakery registration endpoint may not be fully implemented"
|
log_error "Bakery registration failed!"
|
||||||
|
echo "Response: $BAKERY_RESPONSE"
|
||||||
|
# Continue with tests using placeholder UUID for other endpoints
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# STEP 6: TOKEN REFRESH
|
# STEP 5: TEST DATA SERVICE WITH TENANT ID
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
log_step "Step 6: Testing token refresh"
|
log_step "Step 5: Testing data service through gateway"
|
||||||
|
|
||||||
|
# Only test with valid tenant ID
|
||||||
|
if [ "$TENANT_ID" != "00000000-0000-0000-0000-000000000000" ]; then
|
||||||
|
DATA_RESPONSE=$(curl -s -X GET "$API_BASE/api/v1/data/sales" \
|
||||||
|
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||||
|
-H "X-Tenant-ID: $TENANT_ID")
|
||||||
|
|
||||||
|
echo "Data Service Response:"
|
||||||
|
echo "$DATA_RESPONSE" | jq '.'
|
||||||
|
|
||||||
|
if [ "$(echo "$DATA_RESPONSE" | jq -r '.status // "unknown"')" != "error" ]; then
|
||||||
|
log_success "Data service access successful!"
|
||||||
|
else
|
||||||
|
log_warning "Data service returned error (may be expected for new tenant)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warning "Skipping data service test - no valid tenant ID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# STEP 6: TEST TRAINING SERVICE WITH TENANT ID
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
log_step "Step 6: Testing training service through gateway"
|
||||||
|
|
||||||
|
# Only test with valid tenant ID
|
||||||
|
if [ "$TENANT_ID" != "00000000-0000-0000-0000-000000000000" ]; then
|
||||||
|
TRAINING_RESPONSE=$(curl -s -X POST "$API_BASE/api/v1/training/jobs" \
|
||||||
|
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||||
|
-H "X-Tenant-ID: $TENANT_ID" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"include_weather": true,
|
||||||
|
"include_traffic": false,
|
||||||
|
"min_data_points": 30
|
||||||
|
}')
|
||||||
|
|
||||||
|
echo "Training Service Response:"
|
||||||
|
echo "$TRAINING_RESPONSE" | jq '.'
|
||||||
|
|
||||||
|
if echo "$TRAINING_RESPONSE" | jq -e '.job_id // .message' > /dev/null; then
|
||||||
|
log_success "Training service access successful!"
|
||||||
|
else
|
||||||
|
log_warning "Training service access may have issues"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warning "Skipping training service test - no valid tenant ID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# STEP 7: TOKEN REFRESH
|
||||||
|
# ================================================================
|
||||||
|
|
||||||
|
log_step "Step 7: Testing token refresh"
|
||||||
|
|
||||||
REFRESH_RESPONSE=$(curl -s -X POST "$AUTH_BASE/refresh" \
|
REFRESH_RESPONSE=$(curl -s -X POST "$AUTH_BASE/refresh" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -268,19 +294,19 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# STEP 7: DIRECT SERVICE HEALTH CHECKS
|
# STEP 8: DIRECT SERVICE HEALTH CHECKS
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
log_step "Step 7: Testing direct service access (without gateway)"
|
log_step "Step 8: Testing direct service access (without gateway)"
|
||||||
|
|
||||||
# Test auth service directly
|
# Test auth service directly
|
||||||
log_step "7a. Auth service direct health check"
|
log_step "8a. Auth service direct health check"
|
||||||
AUTH_HEALTH=$(curl -s -X GET "http://localhost:8001/health")
|
AUTH_HEALTH=$(curl -s -X GET "http://localhost:8001/health")
|
||||||
echo "Auth Service Health:"
|
echo "Auth Service Health:"
|
||||||
echo "$AUTH_HEALTH" | jq '.'
|
echo "$AUTH_HEALTH" | jq '.'
|
||||||
|
|
||||||
# Test other services if available
|
# Test other services if available
|
||||||
log_step "7b. Other services health check"
|
log_step "8b. Other services health check"
|
||||||
|
|
||||||
services=("8002:Training" "8003:Forecasting" "8004:Data" "8005:Tenant" "8006:Notification")
|
services=("8002:Training" "8003:Forecasting" "8004:Data" "8005:Tenant" "8006:Notification")
|
||||||
|
|
||||||
@@ -299,10 +325,10 @@ done
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# STEP 8: LOGOUT
|
# STEP 9: LOGOUT
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
log_step "Step 8: Logging out user"
|
log_step "Step 9: Logging out user"
|
||||||
|
|
||||||
LOGOUT_RESPONSE=$(curl -s -X POST "$AUTH_BASE/logout" \
|
LOGOUT_RESPONSE=$(curl -s -X POST "$AUTH_BASE/logout" \
|
||||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||||
@@ -342,12 +368,12 @@ echo ""
|
|||||||
echo "Services Tested:"
|
echo "Services Tested:"
|
||||||
echo " 🌐 API Gateway"
|
echo " 🌐 API Gateway"
|
||||||
echo " 🔐 Auth Service"
|
echo " 🔐 Auth Service"
|
||||||
|
echo " 🏢 Tenant Service (bakery registration)"
|
||||||
echo " 📊 Data Service (through gateway)"
|
echo " 📊 Data Service (through gateway)"
|
||||||
echo " 🤖 Training Service (through gateway)"
|
echo " 🤖 Training Service (through gateway)"
|
||||||
echo " 🏢 Tenant Service (bakery registration)"
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ -n "$TENANT_ID" ]; then
|
if [ "$TENANT_ID" != "00000000-0000-0000-0000-000000000000" ]; then
|
||||||
echo "Tenant Created:"
|
echo "Tenant Created:"
|
||||||
echo " 🏪 Tenant ID: $TENANT_ID"
|
echo " 🏪 Tenant ID: $TENANT_ID"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -103,8 +103,14 @@ async def login(
|
|||||||
metrics = get_metrics_collector(request)
|
metrics = get_metrics_collector(request)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Check login attempts
|
# Check login attempts TODO
|
||||||
|
# if not await SecurityManager.check_login_attempts(login_data.email):
|
||||||
|
# if metrics:
|
||||||
|
# metrics.increment_counter("login_failure_total", labels={"reason": "rate_limited"})
|
||||||
|
# raise HTTPException(
|
||||||
|
# status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||||
|
# detail="Too many login attempts. Please try again later."
|
||||||
|
# )
|
||||||
|
|
||||||
# Attempt login
|
# Attempt login
|
||||||
result = await AuthService.login(login_data.email, login_data.password, db)
|
result = await AuthService.login(login_data.email, login_data.password, db)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# services/tenant/app/main.py
|
# services/tenant/app/main.py
|
||||||
"""
|
"""
|
||||||
Tenant Service FastAPI application
|
Tenant Service FastAPI application - FIXED VERSION
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
@@ -47,11 +47,35 @@ async def startup_event():
|
|||||||
"""Initialize service on startup"""
|
"""Initialize service on startup"""
|
||||||
logger.info("Starting Tenant Service...")
|
logger.info("Starting Tenant Service...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# ✅ FIX: Import models to ensure they're registered with SQLAlchemy
|
||||||
|
from app.models.tenants import Tenant, TenantMember, Subscription
|
||||||
|
logger.info("Tenant models imported successfully")
|
||||||
|
|
||||||
|
# ✅ FIX: Create database tables on startup
|
||||||
|
await database_manager.create_tables()
|
||||||
|
logger.info("Tenant database tables created successfully")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to initialize tenant service: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
logger.info("Tenant Service startup completed successfully")
|
||||||
|
|
||||||
@app.on_event("shutdown")
|
@app.on_event("shutdown")
|
||||||
async def shutdown_event():
|
async def shutdown_event():
|
||||||
"""Cleanup on shutdown"""
|
"""Cleanup on shutdown"""
|
||||||
logger.info("Shutting down Tenant Service...")
|
logger.info("Shutting down Tenant Service...")
|
||||||
await database_manager.engine.dispose()
|
|
||||||
|
try:
|
||||||
|
# Close database connections properly
|
||||||
|
if hasattr(database_manager, 'engine') and database_manager.engine:
|
||||||
|
await database_manager.engine.dispose()
|
||||||
|
logger.info("Database connections closed")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during shutdown: {e}")
|
||||||
|
|
||||||
|
logger.info("Tenant Service shutdown completed")
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health_check():
|
async def health_check():
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# services/tenant/app/schemas/tenants.py
|
# services/tenant/app/schemas/tenants.py
|
||||||
"""
|
"""
|
||||||
Tenant schemas
|
Tenant schemas - FIXED VERSION
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from uuid import UUID
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class BakeryRegistration(BaseModel):
|
class BakeryRegistration(BaseModel):
|
||||||
@@ -42,8 +43,8 @@ class BakeryRegistration(BaseModel):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
class TenantResponse(BaseModel):
|
class TenantResponse(BaseModel):
|
||||||
"""Tenant response schema"""
|
"""Tenant response schema - FIXED VERSION"""
|
||||||
id: str
|
id: str # ✅ Keep as str for Pydantic validation
|
||||||
name: str
|
name: str
|
||||||
subdomain: Optional[str]
|
subdomain: Optional[str]
|
||||||
business_type: str
|
business_type: str
|
||||||
@@ -55,9 +56,17 @@ class TenantResponse(BaseModel):
|
|||||||
subscription_tier: str
|
subscription_tier: str
|
||||||
model_trained: bool
|
model_trained: bool
|
||||||
last_training_date: Optional[datetime]
|
last_training_date: Optional[datetime]
|
||||||
owner_id: str
|
owner_id: str # ✅ Keep as str for Pydantic validation
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|
||||||
|
# ✅ FIX: Add custom validator to convert UUID to string
|
||||||
|
@validator('id', 'owner_id', pre=True)
|
||||||
|
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:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
@@ -68,16 +77,70 @@ class TenantAccessResponse(BaseModel):
|
|||||||
permissions: List[str]
|
permissions: List[str]
|
||||||
|
|
||||||
class TenantMemberResponse(BaseModel):
|
class TenantMemberResponse(BaseModel):
|
||||||
"""Tenant member response"""
|
"""Tenant member response - FIXED VERSION"""
|
||||||
id: str
|
id: str
|
||||||
user_id: str
|
user_id: str
|
||||||
role: str
|
role: str
|
||||||
is_active: bool
|
is_active: bool
|
||||||
joined_at: Optional[datetime]
|
joined_at: Optional[datetime]
|
||||||
|
|
||||||
|
# ✅ FIX: Add custom validator to convert UUID to string
|
||||||
|
@validator('id', 'user_id', pre=True)
|
||||||
|
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
|
||||||
|
|
||||||
class TenantUpdate(BaseModel):
|
class TenantUpdate(BaseModel):
|
||||||
"""Tenant update schema"""
|
"""Tenant update schema"""
|
||||||
name: Optional[str] = Field(None, min_length=2, max_length=200)
|
name: Optional[str] = Field(None, min_length=2, max_length=200)
|
||||||
address: Optional[str] = Field(None, min_length=10, max_length=500)
|
address: Optional[str] = Field(None, min_length=10, max_length=500)
|
||||||
phone: Optional[str] = None
|
phone: Optional[str] = None
|
||||||
business_type: Optional[str] = None
|
business_type: Optional[str] = None
|
||||||
|
|
||||||
|
class TenantListResponse(BaseModel):
|
||||||
|
"""Response schema for listing tenants"""
|
||||||
|
tenants: List[TenantResponse]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
per_page: int
|
||||||
|
has_next: bool
|
||||||
|
has_prev: bool
|
||||||
|
|
||||||
|
class TenantMemberInvitation(BaseModel):
|
||||||
|
"""Schema for inviting a member to a tenant"""
|
||||||
|
email: str = Field(..., pattern=r'^[^@]+@[^@]+\.[^@]+$')
|
||||||
|
role: str = Field(..., pattern=r'^(admin|member|viewer)$')
|
||||||
|
message: Optional[str] = Field(None, max_length=500)
|
||||||
|
|
||||||
|
class TenantMemberUpdate(BaseModel):
|
||||||
|
"""Schema for updating tenant member"""
|
||||||
|
role: Optional[str] = Field(None, pattern=r'^(owner|admin|member|viewer)$')
|
||||||
|
is_active: Optional[bool] = None
|
||||||
|
|
||||||
|
class TenantSubscriptionUpdate(BaseModel):
|
||||||
|
"""Schema for updating tenant subscription"""
|
||||||
|
plan: str = Field(..., pattern=r'^(basic|professional|enterprise)$')
|
||||||
|
billing_cycle: str = Field(default="monthly", pattern=r'^(monthly|yearly)$')
|
||||||
|
|
||||||
|
class TenantStatsResponse(BaseModel):
|
||||||
|
"""Tenant statistics response"""
|
||||||
|
tenant_id: str
|
||||||
|
total_members: int
|
||||||
|
active_members: int
|
||||||
|
total_predictions: int
|
||||||
|
models_trained: int
|
||||||
|
last_training_date: Optional[datetime]
|
||||||
|
subscription_plan: str
|
||||||
|
subscription_status: str
|
||||||
|
|
||||||
|
@validator('tenant_id', pre=True)
|
||||||
|
def convert_uuid_to_string(cls, v):
|
||||||
|
"""Convert UUID objects to strings for JSON serialization"""
|
||||||
|
if isinstance(v, UUID):
|
||||||
|
return str(v)
|
||||||
|
return v
|
||||||
@@ -5,6 +5,7 @@ Tenant service messaging for event publishing
|
|||||||
from shared.messaging.rabbitmq import RabbitMQClient
|
from shared.messaging.rabbitmq import RabbitMQClient
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
import structlog
|
import structlog
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
logger = structlog.get_logger()
|
logger = structlog.get_logger()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user