Add subcription feature
This commit is contained in:
@@ -102,6 +102,135 @@ async def register(
|
||||
detail="Registration failed"
|
||||
)
|
||||
|
||||
@router.post("/api/v1/auth/register-with-subscription", response_model=TokenResponse)
|
||||
@track_execution_time("enhanced_registration_with_subscription_duration_seconds", "auth-service")
|
||||
async def register_with_subscription(
|
||||
user_data: UserRegistration,
|
||||
request: Request,
|
||||
auth_service: EnhancedAuthService = Depends(get_auth_service)
|
||||
):
|
||||
"""
|
||||
Register new user and create subscription in one call
|
||||
|
||||
This endpoint implements the new registration flow where:
|
||||
1. User is created
|
||||
2. Payment customer is created via tenant service
|
||||
3. Tenant-independent subscription is created via tenant service
|
||||
4. Subscription data is stored in onboarding progress
|
||||
5. User is authenticated and returned with tokens
|
||||
|
||||
The subscription will be linked to a tenant during the onboarding flow.
|
||||
"""
|
||||
metrics = get_metrics_collector(request)
|
||||
|
||||
logger.info("Registration with subscription attempt using new architecture",
|
||||
email=user_data.email)
|
||||
|
||||
try:
|
||||
# Enhanced input validation
|
||||
if not user_data.email or not user_data.email.strip():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email is required"
|
||||
)
|
||||
|
||||
if not user_data.password or len(user_data.password) < 8:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Password must be at least 8 characters long"
|
||||
)
|
||||
|
||||
if not user_data.full_name or not user_data.full_name.strip():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Full name is required"
|
||||
)
|
||||
|
||||
# Step 1: Register user using enhanced service
|
||||
logger.info("Step 1: Creating user", email=user_data.email)
|
||||
|
||||
result = await auth_service.register_user(user_data)
|
||||
user_id = result.user.id
|
||||
|
||||
logger.info("User created successfully", user_id=user_id)
|
||||
|
||||
# Step 2: Create subscription via tenant service (if subscription data provided)
|
||||
subscription_id = None
|
||||
if user_data.subscription_plan and user_data.payment_method_id:
|
||||
logger.info("Step 2: Creating tenant-independent subscription",
|
||||
user_id=user_id,
|
||||
plan=user_data.subscription_plan)
|
||||
|
||||
subscription_result = await auth_service.create_subscription_via_tenant_service(
|
||||
user_id=user_id,
|
||||
plan_id=user_data.subscription_plan,
|
||||
payment_method_id=user_data.payment_method_id,
|
||||
billing_cycle=user_data.billing_cycle or "monthly",
|
||||
coupon_code=user_data.coupon_code
|
||||
)
|
||||
|
||||
if subscription_result:
|
||||
subscription_id = subscription_result.get("subscription_id")
|
||||
logger.info("Tenant-independent subscription created successfully",
|
||||
user_id=user_id,
|
||||
subscription_id=subscription_id)
|
||||
|
||||
# Step 3: Store subscription data in onboarding progress
|
||||
logger.info("Step 3: Storing subscription data in onboarding progress",
|
||||
user_id=user_id)
|
||||
|
||||
# Update onboarding progress with subscription data
|
||||
await auth_service.save_subscription_to_onboarding_progress(
|
||||
user_id=user_id,
|
||||
subscription_id=subscription_id,
|
||||
registration_data=user_data
|
||||
)
|
||||
|
||||
logger.info("Subscription data stored in onboarding progress",
|
||||
user_id=user_id)
|
||||
else:
|
||||
logger.warning("Subscription creation failed, but user registration succeeded",
|
||||
user_id=user_id)
|
||||
else:
|
||||
logger.info("No subscription data provided, skipping subscription creation",
|
||||
user_id=user_id)
|
||||
|
||||
# Record successful registration
|
||||
if metrics:
|
||||
metrics.increment_counter("enhanced_registration_with_subscription_total", labels={"status": "success"})
|
||||
|
||||
logger.info("Registration with subscription completed successfully using new architecture",
|
||||
user_id=user_id,
|
||||
email=user_data.email,
|
||||
subscription_id=subscription_id)
|
||||
|
||||
# Add subscription_id to the response
|
||||
result.subscription_id = subscription_id
|
||||
return result
|
||||
|
||||
except HTTPException as e:
|
||||
if metrics:
|
||||
error_type = "validation_error" if e.status_code == 400 else "conflict" if e.status_code == 409 else "failed"
|
||||
metrics.increment_counter("enhanced_registration_with_subscription_total", labels={"status": error_type})
|
||||
|
||||
logger.warning("Registration with subscription failed using new architecture",
|
||||
email=user_data.email,
|
||||
error=e.detail)
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
if metrics:
|
||||
metrics.increment_counter("enhanced_registration_with_subscription_total", labels={"status": "error"})
|
||||
|
||||
logger.error("Registration with subscription system error using new architecture",
|
||||
email=user_data.email,
|
||||
error=str(e))
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Registration with subscription failed"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/v1/auth/login", response_model=TokenResponse)
|
||||
@track_execution_time("enhanced_login_duration_seconds", "auth-service")
|
||||
|
||||
@@ -1044,4 +1044,110 @@ async def delete_step_draft(
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to delete step draft"
|
||||
)
|
||||
|
||||
@router.get("/api/v1/auth/me/onboarding/subscription-parameters", response_model=Dict[str, Any])
|
||||
async def get_subscription_parameters(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get subscription parameters saved during onboarding for tenant creation
|
||||
Returns all parameters needed for subscription processing: plan, billing cycle, coupon, etc.
|
||||
"""
|
||||
try:
|
||||
user_id = current_user["user_id"]
|
||||
is_demo = current_user.get("is_demo", False)
|
||||
|
||||
# DEMO FIX: Demo users get default subscription parameters
|
||||
if is_demo or user_id.startswith("demo-user-"):
|
||||
logger.info(f"Demo user {user_id} requesting subscription parameters - returning demo defaults")
|
||||
return {
|
||||
"subscription_plan": "professional",
|
||||
"billing_cycle": "monthly",
|
||||
"coupon_code": "DEMO2025",
|
||||
"payment_method_id": "pm_demo_test_123",
|
||||
"payment_customer_id": "cus_demo_test_123", # Demo payment customer ID
|
||||
"saved_at": datetime.now(timezone.utc).isoformat(),
|
||||
"demo_mode": True
|
||||
}
|
||||
|
||||
# Get subscription parameters from onboarding progress
|
||||
from app.repositories.onboarding_repository import OnboardingRepository
|
||||
onboarding_repo = OnboardingRepository(db)
|
||||
subscription_params = await onboarding_repo.get_subscription_parameters(user_id)
|
||||
|
||||
if not subscription_params:
|
||||
logger.warning(f"No subscription parameters found for user {user_id} - returning defaults")
|
||||
return {
|
||||
"subscription_plan": "starter",
|
||||
"billing_cycle": "monthly",
|
||||
"coupon_code": None,
|
||||
"payment_method_id": None,
|
||||
"payment_customer_id": None,
|
||||
"saved_at": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
||||
logger.info(f"Retrieved subscription parameters for user {user_id}",
|
||||
subscription_plan=subscription_params["subscription_plan"],
|
||||
billing_cycle=subscription_params["billing_cycle"],
|
||||
coupon_code=subscription_params["coupon_code"])
|
||||
|
||||
return subscription_params
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting subscription parameters for user {current_user.get('user_id')}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve subscription parameters"
|
||||
)
|
||||
|
||||
@router.get("/api/v1/auth/users/{user_id}/onboarding/subscription-parameters", response_model=Dict[str, Any])
|
||||
async def get_user_subscription_parameters(
|
||||
user_id: str,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get subscription parameters for a specific user (admin/service access)
|
||||
"""
|
||||
try:
|
||||
# Check permissions - only admins and services can access other users' data
|
||||
requester_id = current_user["user_id"]
|
||||
requester_roles = current_user.get("roles", [])
|
||||
is_service = current_user.get("is_service", False)
|
||||
|
||||
if not is_service and "super_admin" not in requester_roles and requester_id != user_id:
|
||||
logger.warning(f"Unauthorized access attempt to user {user_id} subscription parameters by {requester_id}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Insufficient permissions to access other users' subscription parameters"
|
||||
)
|
||||
|
||||
# Get subscription parameters from onboarding progress
|
||||
from app.repositories.onboarding_repository import OnboardingRepository
|
||||
onboarding_repo = OnboardingRepository(db)
|
||||
subscription_params = await onboarding_repo.get_subscription_parameters(user_id)
|
||||
|
||||
if not subscription_params:
|
||||
logger.warning(f"No subscription parameters found for user {user_id} - returning defaults")
|
||||
return {
|
||||
"subscription_plan": "starter",
|
||||
"billing_cycle": "monthly",
|
||||
"coupon_code": None,
|
||||
"payment_method_id": None,
|
||||
"payment_customer_id": None,
|
||||
"saved_at": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
||||
logger.info(f"Retrieved subscription parameters for user {user_id} by {requester_id}",
|
||||
subscription_plan=subscription_params["subscription_plan"])
|
||||
|
||||
return subscription_params
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting subscription parameters for user {user_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve subscription parameters"
|
||||
)
|
||||
@@ -2,7 +2,7 @@
|
||||
User management API routes
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks, Path
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks, Path, Body
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import Dict, Any
|
||||
import structlog
|
||||
@@ -223,7 +223,9 @@ async def get_user_by_id(
|
||||
created_at=user.created_at,
|
||||
last_login=user.last_login,
|
||||
role=user.role,
|
||||
tenant_id=None
|
||||
tenant_id=None,
|
||||
payment_customer_id=user.payment_customer_id,
|
||||
default_payment_method_id=user.default_payment_method_id
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
@@ -481,3 +483,71 @@ async def get_user_activity(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get user activity information"
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/api/v1/auth/users/{user_id}/tenant")
|
||||
async def update_user_tenant(
|
||||
user_id: str = Path(..., description="User ID"),
|
||||
tenant_data: Dict[str, Any] = Body(..., description="Tenant data containing tenant_id"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Update user's tenant_id after tenant registration
|
||||
|
||||
This endpoint is called by the tenant service after a user creates their tenant.
|
||||
It links the user to their newly created tenant.
|
||||
"""
|
||||
try:
|
||||
# Log the incoming request data for debugging
|
||||
logger.debug("Received tenant update request",
|
||||
user_id=user_id,
|
||||
tenant_data=tenant_data)
|
||||
|
||||
tenant_id = tenant_data.get("tenant_id")
|
||||
|
||||
if not tenant_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="tenant_id is required"
|
||||
)
|
||||
|
||||
logger.info("Updating user tenant_id",
|
||||
user_id=user_id,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
user_service = UserService(db)
|
||||
user = await user_service.get_user_by_id(uuid.UUID(user_id))
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
# Update user's tenant_id
|
||||
user.tenant_id = uuid.UUID(tenant_id)
|
||||
user.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
logger.info("Successfully updated user tenant_id",
|
||||
user_id=user_id,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"user_id": str(user.id),
|
||||
"tenant_id": str(user.tenant_id)
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Failed to update user tenant_id",
|
||||
user_id=user_id,
|
||||
error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to update user tenant_id"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user