Add subcription feature 7

This commit is contained in:
Urtzi Alfaro
2026-01-16 15:21:11 +01:00
parent 4bafceed0d
commit 5e01b34cc0
5 changed files with 0 additions and 1154 deletions

View File

@@ -1,78 +0,0 @@
# User Endpoint Refactoring - Changes Summary
## Overview
This refactoring removes the redundant `/auth/me` endpoint and consolidates user profile retrieval through the proper `/users/{user_id}` endpoint, improving API consistency and reducing code duplication.
## Problem Analysis
The system had two endpoints returning similar user information:
- `/auth/me` - Returned current user from JWT token
- `/users/{user_id}` - Returned user by ID
This created redundancy and confusion in the API structure.
## Changes Made
### 1. Backend Changes
**File:** `services/auth/app/api/auth_operations.py`
- **Removed:** `/auth/me` endpoint (lines with `@router.get("/me")`)
- **Impact:** The endpoint no longer exists in the auth service
- **Reason:** Redundant with `/users/{user_id}` endpoint
### 2. Frontend Changes
**File:** `frontend/src/api/services/user.ts`
- **Updated:** `getCurrentUser()` method
- **Before:** Called `/users/me`
- **After:** Gets current user ID from auth store and calls `/users/{user_id}`
- **Implementation:**
```typescript
async getCurrentUser(): Promise<UserResponse> {
// Get current user ID from auth store
const authStore = useAuthStore.getState();
const userId = authStore.user?.id;
if (!userId) {
throw new Error('No authenticated user found');
}
return apiClient.get<UserResponse>(`${this.baseUrl}/${userId}`);
}
```
### 3. API Client Changes
**File:** `frontend/src/api/client/apiClient.ts`
- **Updated:** Removed `/auth/me` from `noTenantEndpoints` array
- **Before:** `/auth/me` was listed as a user-level endpoint
- **After:** Removed since the endpoint no longer exists
- **Note:** `/auth/me/onboarding` remains as it's a different endpoint
## API Gateway Behavior
The gateway routing remains unchanged and works correctly:
- Frontend calls `/users/{user_id}`
- Gateway forwards to `/api/v1/auth/users/{user_id}` in auth service
- Auth service returns user data via `get_user_by_id()` endpoint
## Benefits
1. **Consistency:** Single source of truth for user data
2. **Simplicity:** Removes redundant endpoint
3. **Maintainability:** Clearer API structure
4. **Performance:** No duplicate data fetching logic
## Testing
- Created verification script to ensure all changes are syntactically correct
- Verified that `/auth/me` endpoint has been removed
- Confirmed that UserService correctly uses user ID from auth store
- Validated that API client no longer references the removed endpoint
## Migration Notes
- **Breaking Change:** Any direct calls to `/auth/me` will now return 404
- **Replacement:** Use `/users/{user_id}` with the current user's ID
- **Frontend:** All existing frontend code using `useCurrentUser()` continues to work
- **Backend:** Other services should use `/users/{user_id}` for user data
## Files Modified
1. `services/auth/app/api/auth_operations.py` - Removed endpoint
2. `frontend/src/api/services/user.ts` - Updated service method
3. `frontend/src/api/client/apiClient.ts` - Updated endpoint configuration
## Verification
All changes have been verified with the verification script and pass syntax checks.

View File

@@ -1,361 +0,0 @@
# Subscription System Architecture Redesign - IMPLEMENTED
## Overview
This document outlines the **completed implementation** of the comprehensive subscription system architecture redesign. The implementation improves organization, maintainability, and separation of concerns while respecting the existing service-based client architecture.
## Current State - FULLY IMPLEMENTED ✅
### Issues Resolved
1.**Mixed Concerns**: Subscription endpoints are now properly separated from tenant management
2.**Inconsistent URL Patterns**: All endpoints now use consistent `/tenants/{tenant_id}/subscription/*` structure
3.**Poor Organization**: Subscription operations are now centralized in one API file
4.**Middleware Dependencies**: All middleware now uses updated URL patterns
5.**Client Layer Confusion**: Tenant client contains only tenant-specific methods, subscription methods use proper patterns
## New Architecture Design - FULLY IMPLEMENTED ✅
### API Structure & URL Patterns
#### Public Endpoints (No Authentication) - ✅ IMPLEMENTED
```
GET /api/v1/plans - Get all available plans
GET /api/v1/plans/{tier} - Get specific plan details
GET /api/v1/plans/{tier}/features - Get plan features
GET /api/v1/plans/{tier}/limits - Get plan limits
GET /api/v1/plans/compare - Compare all plans
```
#### Registration Flow (No Tenant Context) - ✅ IMPLEMENTED
```
POST /api/v1/registration/payment-setup - Start registration with payment
POST /api/v1/registration/complete - Complete registration after 3DS
GET /api/v1/registration/state/{state_id} - Check registration state
```
#### Tenant-Dependent Subscription Endpoints - ✅ IMPLEMENTED
```
GET /api/v1/tenants/{tenant_id}/subscription/status - Get subscription status
GET /api/v1/tenants/{tenant_id}/subscription/details - Get full subscription details
GET /api/v1/tenants/{tenant_id}/subscription/tier - Get subscription tier (cached)
GET /api/v1/tenants/{tenant_id}/subscription/limits - Get subscription limits
GET /api/v1/tenants/{tenant_id}/subscription/usage - Get usage summary
GET /api/v1/tenants/{tenant_id}/subscription/features/{feature} - Check feature access
# Subscription Management
POST /api/v1/tenants/{tenant_id}/subscription/cancel - Cancel subscription
POST /api/v1/tenants/{tenant_id}/subscription/reactivate - Reactivate subscription
GET /api/v1/tenants/{tenant_id}/subscription/validate-upgrade/{new_plan} - Validate upgrade
POST /api/v1/tenants/{tenant_id}/subscription/upgrade - Upgrade subscription
# Quota & Limit Checks
GET /api/v1/tenants/{tenant_id}/subscription/limits/locations - Check location limits
GET /api/v1/tenants/{tenant_id}/subscription/limits/products - Check product limits
GET /api/v1/tenants/{tenant_id}/subscription/limits/users - Check user limits
GET /api/v1/tenants/{tenant_id}/subscription/limits/recipes - Check recipe limits
GET /api/v1/tenants/{tenant_id}/subscription/limits/suppliers - Check supplier limits
# Payment Management
GET /api/v1/tenants/{tenant_id}/subscription/payment-method - Get payment method
POST /api/v1/tenants/{tenant_id}/subscription/payment-method - Update payment method
GET /api/v1/tenants/{tenant_id}/subscription/invoices - Get invoices
```
### Service Layer Organization - ✅ IMPLEMENTED
**`subscription.py`** - All subscription-related endpoints
- Registration flow endpoints
- Tenant-dependent subscription endpoints
- Subscription management endpoints
- Quota and limit checks
- Payment management endpoints
**`tenant_operations.py`** - Only tenant-centric operations
- Tenant creation/management
- Tenant hierarchy operations
- Tenant settings management
- Tenant location management
**`plans.py`** - Public plan information (unchanged)
## Implementation Status - COMPLETE ✅
### Phase 1: Backend API Reorganization - ✅ COMPLETED
#### Step 1: Move Subscription Endpoints - ✅ DONE
- ✅ Moved all subscription endpoints from `tenant_operations.py` to `subscription.py`
- ✅ Implemented new URL patterns with consistent structure
- ✅ Updated all endpoint implementations to use new paths
**Files Modified:**
- `services/tenant/app/api/subscription.py` - ✅ All subscription endpoints added
- `services/tenant/app/api/tenant_operations.py` - ✅ Subscription endpoints removed
#### Step 2: Update Tenant Service Main.py - ✅ DONE
- ✅ Ensured proper router inclusion for new subscription endpoints
- ✅ Removed old subscription endpoints from tenant operations router
**Files Modified:**
- `services/tenant/app/main.py` - ✅ Router inclusion updated
### Phase 2: Update Tenant Client - ✅ COMPLETED
#### Step 3: Update Tenant Client URL Patterns - ✅ DONE
All subscription-related methods in `shared/clients/tenant_client.py` updated:
```python
# ✅ All methods already updated with new URL patterns
async def get_subscription_status(self, tenant_id: str) -> Optional[Dict[str, Any]]:
result = await self.get(f"tenants/{tenant_id}/subscription/status")
return result
async def get_subscription_details(self, tenant_id: str) -> Optional[Dict[str, Any]]:
result = await self.get(f"tenants/{tenant_id}/subscription/details")
return result
# ✅ All other subscription methods similarly updated
```
**Files Modified:**
- `shared/clients/tenant_client.py` - ✅ All subscription methods updated
### Phase 3: Gateway Updates - ✅ COMPLETED
#### Step 4: Update Gateway Routes - ✅ DONE
```python
# ✅ All gateway routes updated with new patterns
@router.get("/plans")
@router.get("/plans/{tier}")
@router.get("/plans/{tier}/features")
@router.get("/plans/{tier}/limits")
@router.get("/plans/compare")
# ✅ Registration flow
@router.post("/registration/payment-setup")
@router.post("/registration/complete")
@router.get("/registration/state/{state_id}")
# ✅ Tenant subscription endpoints
@router.get("/tenants/{tenant_id}/subscription/status")
@router.get("/tenants/{tenant_id}/subscription/details")
@router.get("/tenants/{tenant_id}/subscription/tier")
@router.get("/tenants/{tenant_id}/subscription/limits")
@router.get("/tenants/{tenant_id}/subscription/usage")
@router.get("/tenants/{tenant_id}/subscription/features/{feature}")
@router.post("/tenants/{tenant_id}/subscription/cancel")
@router.post("/tenants/{tenant_id}/subscription/reactivate")
@router.get("/tenants/{tenant_id}/subscription/validate-upgrade/{new_plan}")
@router.post("/tenants/{tenant_id}/subscription/upgrade")
@router.get("/tenants/{tenant_id}/subscription/limits/locations")
@router.get("/tenants/{tenant_id}/subscription/limits/products")
@router.get("/tenants/{tenant_id}/subscription/limits/users")
@router.get("/tenants/{tenant_id}/subscription/limits/recipes")
@router.get("/tenants/{tenant_id}/subscription/limits/suppliers")
@router.get("/tenants/{tenant_id}/subscription/payment-method")
@router.post("/tenants/{tenant_id}/subscription/payment-method")
@router.get("/tenants/{tenant_id}/subscription/invoices")
```
**Files Modified:**
- `gateway/app/routes/subscription.py` - ✅ All gateway routes updated
#### Step 5: Update Middleware URL Patterns - ✅ DONE
```python
# ✅ In gateway/app/middleware/auth.py
async def _get_tenant_subscription_tier(self, tenant_id: str, request: Request) -> Optional[str]:
response = await self._make_request(
"GET",
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/subscription/tier"
)
```
```python
# ✅ In gateway/app/middleware/read_only_mode.py
async def check_subscription_status(self, tenant_id: str, authorization: str) -> dict:
response = await self._make_request(
"GET",
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/subscription/status"
)
```
**Files Modified:**
- `gateway/app/middleware/subscription.py` - ✅ URL patterns updated
- `gateway/app/middleware/auth.py` - ✅ Subscription tier lookup URL updated
- `gateway/app/middleware/read_only_mode.py` - ✅ Subscription status check URL updated
- `gateway/app/middleware/rate_limiting.py` - ✅ Subscription tier URL fixed
- `gateway/app/routes/tenant.py` - ✅ Removed conflicting wildcard route
### Phase 4: Frontend Updates - ✅ COMPLETED
#### Step 6: Update Frontend Subscription Service - ✅ DONE
```typescript
// ✅ In frontend/src/api/services/subscription.ts
export class SubscriptionService {
// ✅ All methods updated to use new URL patterns
async getSubscriptionStatus(tenantId: string): Promise<UsageSummary> {
return apiClient.get<UsageSummary>(`/tenants/${tenantId}/subscription/status`);
}
async getSubscriptionDetails(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/details`);
}
async getSubscriptionTier(tenantId: string): Promise<string> {
return apiClient.get(`/tenants/${tenantId}/subscription/tier`);
}
async getUsageSummary(tenantId: string): Promise<UsageSummary> {
return apiClient.get<UsageSummary>(`/tenants/${tenantId}/subscription/usage`);
}
async checkFeatureAccess(tenantId: string, featureName: string): Promise<FeatureCheckResponse> {
return apiClient.get<FeatureCheckResponse>(`/tenants/${tenantId}/subscription/features/${featureName}`);
}
async cancelSubscription(tenantId: string, reason?: string): Promise<any> {
return apiClient.post(`/tenants/${tenantId}/subscription/cancel`, { reason });
}
async reactivateSubscription(tenantId: string, plan: string = 'starter'): Promise<any> {
return apiClient.post(`/tenants/${tenantId}/subscription/reactivate`, { plan });
}
async validatePlanUpgrade(tenantId: string, newPlan: string): Promise<PlanUpgradeValidation> {
return apiClient.get<PlanUpgradeValidation>(`/tenants/${tenantId}/subscription/validate-upgrade/${newPlan}`);
}
async upgradePlan(tenantId: string, newPlan: string): Promise<PlanUpgradeResult> {
return apiClient.post<PlanUpgradeResult>(`/tenants/${tenantId}/subscription/upgrade`, { new_plan: newPlan });
}
async canAddLocation(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/locations`);
}
async canAddProduct(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/products`);
}
async canAddUser(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/users`);
}
async canAddRecipe(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/recipes`);
}
async canAddSupplier(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/suppliers`);
}
async getPaymentMethod(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/payment-method`);
}
async updatePaymentMethod(tenantId: string, paymentMethodId: string): Promise<any> {
return apiClient.post(`/tenants/${tenantId}/subscription/payment-method`, { payment_method_id: paymentMethodId });
}
async getInvoices(tenantId: string): Promise<any[]> {
return apiClient.get(`/tenants/${tenantId}/subscription/invoices`);
}
// ✅ Registration flow methods
async startRegistrationPaymentSetup(userData: any): Promise<any> {
return apiClient.post('/registration/payment-setup', userData);
}
async completeRegistration(setupIntentId: string, userData: any): Promise<any> {
return apiClient.post('/registration/complete', {
setup_intent_id: setupIntentId,
user_data: userData
});
}
}
```
**Files Modified:**
- `frontend/src/api/services/subscription.ts` - ✅ All URL patterns updated
- `frontend/src/api/services/tenant.ts` - ✅ Fixed subscription linking endpoint
## Additional Fixes - ✅ COMPLETED
### Service Integration Updates
**Files Modified:**
- `services/auth/app/utils/subscription_fetcher.py` - ✅ Fixed subscription details URLs
- `services/auth/app/api/account_deletion.py` - ✅ Fixed subscription status URL
- `services/inventory/app/api/ingredients.py` - ✅ Fixed quota check URLs
- `services/tenant/tests/integration/test_subscription_creation_flow.py` - ✅ Fixed test URLs
## Benefits Achieved ✅
1. **Clear Separation of Concerns**: Subscription operations are properly separated from tenant management operations
2. **Consistent URL Patterns**: All subscription endpoints follow a logical, standardized structure
3. **Better Organization**: Easier to find and maintain subscription-related code
4. **Service-Based Architecture**: Maintains the constraint of using tenant client only
5. **Improved Maintainability**: Changes to subscription logic are localized to one API file
6. **Better Performance**: Clear caching strategies for subscription data
7. **Easier Scaling**: Subscription endpoints can be scaled independently if needed
8. **Cleaner Codebase**: No mixed concerns between tenant and subscription operations
## Implementation Summary - COMPLETE ✅
### Files Modified
**Backend Services:**
- `services/tenant/app/api/subscription.py` - ✅ Add all subscription endpoints
- `services/tenant/app/api/tenant_operations.py` - ✅ Remove subscription endpoints
- `services/tenant/app/main.py` - ✅ Update router inclusion
**Shared Client Layer:**
- `shared/clients/tenant_client.py` - ✅ Update all subscription methods with new URL patterns
**Gateway Layer:**
- `gateway/app/routes/subscription.py` - ✅ Update all gateway routes with new patterns
- `gateway/app/routes/tenant.py` - ✅ Remove conflicting wildcard route
- `gateway/app/middleware/subscription.py` - ✅ Update URL patterns
- `gateway/app/middleware/auth.py` - ✅ Update subscription tier lookup URL
- `gateway/app/middleware/read_only_mode.py` - ✅ Update subscription status check URL
- `gateway/app/middleware/rate_limiting.py` - ✅ Fix subscription tier URL
**Frontend Layer:**
- `frontend/src/api/services/subscription.ts` - ✅ Update all URL patterns
- `frontend/src/api/services/tenant.ts` - ✅ Fix subscription linking endpoint
**Service Integration:**
- `services/auth/app/utils/subscription_fetcher.py` - ✅ Fix subscription details URLs
- `services/auth/app/api/account_deletion.py` - ✅ Fix subscription status URL
- `services/inventory/app/api/ingredients.py` - ✅ Fix quota check URLs
- `services/tenant/tests/integration/test_subscription_creation_flow.py` - ✅ Fix test URLs
### Implementation Order
1. **Backend API Reorganization** (Phase 1) - ✅ COMPLETED
2. **Update Tenant Client** (Phase 2) - ✅ COMPLETED
3. **Gateway Updates** (Phase 3) - ✅ COMPLETED
4. **Frontend Updates** (Phase 4) - ✅ COMPLETED
5. **Service Integration Fixes** - ✅ COMPLETED
## Verification Status ✅
- ✅ All backend endpoints implemented and tested
- ✅ All client methods updated and verified
- ✅ All gateway routes updated and verified
- ✅ All frontend services updated and verified
- ✅ All middleware updated and verified
- ✅ All service integrations updated and verified
- ✅ No remaining old URL patterns in production code
- ✅ All tests updated to use new patterns
## Migration Complete ✅
The subscription system architecture redesign has been **fully implemented** and is ready for production use. All components are using the new, consistent URL patterns as specified in the architecture redesign. The system maintains proper separation of concerns while respecting the existing service-based client architecture constraint.
**Status: PRODUCTION READY 🚀**

View File

@@ -1,396 +0,0 @@
# Subscription System Architecture Redesign
## Overview
This document outlines a comprehensive redesign of the subscription system architecture to improve organization, maintainability, and separation of concerns while respecting the existing service-based client architecture.
## Current Issues
1. **Mixed Concerns**: Subscription endpoints are scattered across different API files
2. **Inconsistent URL Patterns**: Mixed use of path vs query parameters for tenant IDs
3. **Poor Organization**: Subscription operations mixed with tenant management operations
4. **Middleware Dependencies**: Hardcoded URLs that need updating
5. **Client Layer Confusion**: Tenant client contains many subscription-specific methods
## New Architecture Design
### API Structure & URL Patterns
#### Public Endpoints (No Authentication)
```
GET /api/v1/plans - Get all available plans
GET /api/v1/plans/{tier} - Get specific plan details
GET /api/v1/plans/{tier}/features - Get plan features
GET /api/v1/plans/{tier}/limits - Get plan limits
GET /api/v1/plans/compare - Compare all plans
```
#### Registration Flow (No Tenant Context)
```
POST /api/v1/registration/payment-setup - Start registration with payment
POST /api/v1/registration/complete - Complete registration after 3DS
GET /api/v1/registration/state/{state_id} - Check registration state
```
#### Tenant-Dependent Subscription Endpoints
```
GET /api/v1/tenants/{tenant_id}/subscription/status - Get subscription status
GET /api/v1/tenants/{tenant_id}/subscription/details - Get full subscription details
GET /api/v1/tenants/{tenant_id}/subscription/tier - Get subscription tier (cached)
GET /api/v1/tenants/{tenant_id}/subscription/limits - Get subscription limits
GET /api/v1/tenants/{tenant_id}/subscription/usage - Get usage summary
GET /api/v1/tenants/{tenant_id}/subscription/features/{feature} - Check feature access
# Subscription Management
POST /api/v1/tenants/{tenant_id}/subscription/cancel - Cancel subscription
POST /api/v1/tenants/{tenant_id}/subscription/reactivate - Reactivate subscription
GET /api/v1/tenants/{tenant_id}/subscription/validate-upgrade/{new_plan} - Validate upgrade
POST /api/v1/tenants/{tenant_id}/subscription/upgrade - Upgrade subscription
# Quota & Limit Checks
GET /api/v1/tenants/{tenant_id}/subscription/limits/locations - Check location limits
GET /api/v1/tenants/{tenant_id}/subscription/limits/products - Check product limits
GET /api/v1/tenants/{tenant_id}/subscription/limits/users - Check user limits
GET /api/v1/tenants/{tenant_id}/subscription/limits/recipes - Check recipe limits
GET /api/v1/tenants/{tenant_id}/subscription/limits/suppliers - Check supplier limits
# Payment Management
GET /api/v1/tenants/{tenant_id}/subscription/payment-method - Get payment method
POST /api/v1/tenants/{tenant_id}/subscription/payment-method - Update payment method
GET /api/v1/tenants/{tenant_id}/subscription/invoices - Get invoices
```
### Service Layer Organization
**`subscription.py`** - All subscription-related endpoints
- Registration flow endpoints
- Tenant-dependent subscription endpoints
- Subscription management endpoints
- Quota and limit checks
- Payment management endpoints
**`tenant_operations.py`** - Only tenant-centric operations
- Tenant creation/management
- Tenant hierarchy operations
- Tenant settings management
- Tenant location management
**`plans.py`** - Public plan information (unchanged)
## Implementation Plan
### Phase 1: Backend API Reorganization
#### Step 1: Move Subscription Endpoints
- Move all subscription endpoints from `tenant_operations.py` to `subscription.py`
- Implement new URL patterns with consistent structure
- Update all endpoint implementations to use new paths
**Files to modify:**
- `services/tenant/app/api/subscription.py` - Add all subscription endpoints
- `services/tenant/app/api/tenant_operations.py` - Remove subscription endpoints
#### Step 2: Update Tenant Service Main.py
- Ensure proper router inclusion for new subscription endpoints
- Remove old subscription endpoints from tenant operations router
**Files to modify:**
- `services/tenant/app/main.py` - Update router inclusion
### Phase 2: Update Tenant Client
#### Step 3: Update Tenant Client URL Patterns
Update all subscription-related methods in `shared/clients/tenant_client.py`:
```python
async def get_subscription_status(self, tenant_id: str) -> Optional[Dict[str, Any]]:
# Updated URL pattern
result = await self.get(f"tenants/{tenant_id}/subscription/status")
return result
async def get_subscription_details(self, tenant_id: str) -> Optional[Dict[str, Any]]:
# Updated URL pattern
result = await self.get(f"tenants/{tenant_id}/subscription/details")
return result
# Update all other subscription methods similarly
async def get_subscription_tier(self, tenant_id: str) -> Optional[str]:
result = await self.get(f"tenants/{tenant_id}/subscription/tier")
return result.get('tier') if result else None
async def get_subscription_limits(self, tenant_id: str) -> Optional[Dict[str, Any]]:
return await self.get(f"tenants/{tenant_id}/subscription/limits")
async def get_usage_summary(self, tenant_id: str) -> Optional[Dict[str, Any]]:
return await self.get(f"tenants/{tenant_id}/subscription/usage")
async def has_feature(self, tenant_id: str, feature: str) -> bool:
result = await self.get(f"tenants/{tenant_id}/subscription/features/{feature}")
return result.get('has_feature', False) if result else False
# Registration flow methods
async def start_registration_payment_setup(self, user_data: Dict[str, Any]) -> Dict[str, Any]:
result = await self.post("registration/payment-setup", user_data)
return result
async def complete_registration(self, setup_intent_id: str, user_data: Dict[str, Any]) -> Dict[str, Any]:
result = await self.post("registration/complete", {
"setup_intent_id": setup_intent_id,
"user_data": user_data
})
return result
# Update quota check methods
async def can_add_location(self, tenant_id: str) -> Dict[str, Any]:
return await self.get(f"tenants/{tenant_id}/subscription/limits/locations")
async def can_add_product(self, tenant_id: str) -> Dict[str, Any]:
return await self.get(f"tenants/{tenant_id}/subscription/limits/products")
async def can_add_user(self, tenant_id: str) -> Dict[str, Any]:
return await self.get(f"tenants/{tenant_id}/subscription/limits/users")
async def can_add_recipe(self, tenant_id: str) -> Dict[str, Any]:
return await self.get(f"tenants/{tenant_id}/subscription/limits/recipes")
async def can_add_supplier(self, tenant_id: str) -> Dict[str, Any]:
return await self.get(f"tenants/{tenant_id}/subscription/limits/suppliers")
# Update subscription management methods
async def cancel_subscription(self, tenant_id: str, reason: str = "") -> Dict[str, Any]:
return await self.post(f"tenants/{tenant_id}/subscription/cancel", {"reason": reason})
async def reactivate_subscription(self, tenant_id: str, plan: str = "starter") -> Dict[str, Any]:
return await self.post(f"tenants/{tenant_id}/subscription/reactivate", {"plan": plan})
async def validate_plan_upgrade(self, tenant_id: str, new_plan: str) -> Dict[str, Any]:
return await self.get(f"tenants/{tenant_id}/subscription/validate-upgrade/{new_plan}")
async def upgrade_subscription_plan(self, tenant_id: str, new_plan: str) -> Dict[str, Any]:
return await self.post(f"tenants/{tenant_id}/subscription/upgrade", {"new_plan": new_plan})
# Update payment management methods
async def get_payment_method(self, tenant_id: str) -> Optional[Dict[str, Any]]:
return await self.get(f"tenants/{tenant_id}/subscription/payment-method")
async def update_payment_method(self, tenant_id: str, payment_method_id: str) -> Dict[str, Any]:
return await self.post(f"tenants/{tenant_id}/subscription/payment-method", {
"payment_method_id": payment_method_id
})
async def get_invoices(self, tenant_id: str) -> Optional[List[Dict[str, Any]]]:
return await self.get(f"tenants/{tenant_id}/subscription/invoices")
```
**Files to modify:**
- `shared/clients/tenant_client.py` - Update all subscription methods with new URL patterns
### Phase 3: Gateway Updates
#### Step 4: Update Gateway Routes
```python
# In gateway/app/routes/subscription.py
# Public endpoints
@router.get("/plans")
@router.get("/plans/{tier}")
@router.get("/plans/{tier}/features")
@router.get("/plans/{tier}/limits")
@router.get("/plans/compare")
# Registration flow
@router.post("/registration/payment-setup")
@router.post("/registration/complete")
@router.get("/registration/state/{state_id}")
# Tenant subscription endpoints
@router.get("/tenants/{tenant_id}/subscription/status")
@router.get("/tenants/{tenant_id}/subscription/details")
@router.get("/tenants/{tenant_id}/subscription/tier")
@router.get("/tenants/{tenant_id}/subscription/limits")
@router.get("/tenants/{tenant_id}/subscription/usage")
@router.get("/tenants/{tenant_id}/subscription/features/{feature}")
@router.post("/tenants/{tenant_id}/subscription/cancel")
@router.post("/tenants/{tenant_id}/subscription/reactivate")
@router.get("/tenants/{tenant_id}/subscription/validate-upgrade/{new_plan}")
@router.post("/tenants/{tenant_id}/subscription/upgrade")
@router.get("/tenants/{tenant_id}/subscription/limits/locations")
@router.get("/tenants/{tenant_id}/subscription/limits/products")
@router.get("/tenants/{tenant_id}/subscription/limits/users")
@router.get("/tenants/{tenant_id}/subscription/limits/recipes")
@router.get("/tenants/{tenant_id}/subscription/limits/suppliers")
@router.get("/tenants/{tenant_id}/subscription/payment-method")
@router.post("/tenants/{tenant_id}/subscription/payment-method")
@router.get("/tenants/{tenant_id}/subscription/invoices")
```
**Files to modify:**
- `gateway/app/routes/subscription.py` - Update all gateway routes with new patterns
#### Step 5: Update Middleware URL Patterns
```python
# In gateway/app/middleware/auth.py
async def _get_tenant_subscription_tier(self, tenant_id: str, request: Request) -> Optional[str]:
try:
# Updated URL pattern
response = await self._make_request(
"GET",
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/subscription/tier"
)
# ... rest of method remains the same
```
```python
# In gateway/app/middleware/read_only_mode.py
async def check_subscription_status(self, tenant_id: str, authorization: str) -> dict:
try:
# Updated URL pattern
response = await self._make_request(
"GET",
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/subscription/status"
)
# ... rest of method remains the same
```
**Files to modify:**
- `gateway/app/middleware/subscription.py` - Update URL patterns
- `gateway/app/middleware/auth.py` - Update subscription tier lookup URL
- `gateway/app/middleware/read_only_mode.py` - Update subscription status check URL
### Phase 4: Frontend Updates
#### Step 6: Update Frontend Subscription Service
```typescript
// In frontend/src/api/services/subscription.ts
export class SubscriptionService {
// Update all methods to use new URL patterns
async getSubscriptionStatus(tenantId: string): Promise<UsageSummary> {
return apiClient.get<UsageSummary>(`/tenants/${tenantId}/subscription/status`);
}
async getSubscriptionDetails(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/details`);
}
async getSubscriptionTier(tenantId: string): Promise<string> {
return apiClient.get(`/tenants/${tenantId}/subscription/tier`);
}
async getUsageSummary(tenantId: string): Promise<UsageSummary> {
return apiClient.get<UsageSummary>(`/tenants/${tenantId}/subscription/usage`);
}
async checkFeatureAccess(tenantId: string, featureName: string): Promise<FeatureCheckResponse> {
return apiClient.get<FeatureCheckResponse>(`/tenants/${tenantId}/subscription/features/${featureName}`);
}
async cancelSubscription(tenantId: string, reason?: string): Promise<any> {
return apiClient.post(`/tenants/${tenantId}/subscription/cancel`, { reason });
}
async reactivateSubscription(tenantId: string, plan: string = 'starter'): Promise<any> {
return apiClient.post(`/tenants/${tenantId}/subscription/reactivate`, { plan });
}
async validatePlanUpgrade(tenantId: string, newPlan: string): Promise<PlanUpgradeValidation> {
return apiClient.get<PlanUpgradeValidation>(`/tenants/${tenantId}/subscription/validate-upgrade/${newPlan}`);
}
async upgradePlan(tenantId: string, newPlan: string): Promise<PlanUpgradeResult> {
return apiClient.post<PlanUpgradeResult>(`/tenants/${tenantId}/subscription/upgrade`, { new_plan: newPlan });
}
async canAddLocation(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/locations`);
}
async canAddProduct(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/products`);
}
async canAddUser(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/users`);
}
async canAddRecipe(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/recipes`);
}
async canAddSupplier(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/limits/suppliers`);
}
async getPaymentMethod(tenantId: string): Promise<any> {
return apiClient.get(`/tenants/${tenantId}/subscription/payment-method`);
}
async updatePaymentMethod(tenantId: string, paymentMethodId: string): Promise<any> {
return apiClient.post(`/tenants/${tenantId}/subscription/payment-method`, { payment_method_id: paymentMethodId });
}
async getInvoices(tenantId: string): Promise<any[]> {
return apiClient.get(`/tenants/${tenantId}/subscription/invoices`);
}
// Registration flow methods
async startRegistrationPaymentSetup(userData: any): Promise<any> {
return apiClient.post('/registration/payment-setup', userData);
}
async completeRegistration(setupIntentId: string, userData: any): Promise<any> {
return apiClient.post('/registration/complete', {
setup_intent_id: setupIntentId,
user_data: userData
});
}
}
```
**Files to modify:**
- `frontend/src/api/services/subscription.ts` - Update all URL patterns
## Benefits of New Architecture
1. **Clear Separation of Concerns**: Subscription operations are properly separated from tenant management operations
2. **Consistent URL Patterns**: All subscription endpoints follow a logical, standardized structure
3. **Better Organization**: Easier to find and maintain subscription-related code
4. **Service-Based Architecture**: Maintains the constraint of using tenant client only
5. **Improved Maintainability**: Changes to subscription logic are localized to one API file
6. **Better Performance**: Clear caching strategies for subscription data
7. **Easier Scaling**: Subscription endpoints can be scaled independently if needed
8. **Cleaner Codebase**: No mixed concerns between tenant and subscription operations
## Implementation Summary
### Files to Modify
**Backend Services:**
- `services/tenant/app/api/subscription.py` - Add all subscription endpoints
- `services/tenant/app/api/tenant_operations.py` - Remove subscription endpoints
- `services/tenant/app/main.py` - Update router inclusion
**Shared Client Layer:**
- `shared/clients/tenant_client.py` - Update all subscription methods with new URL patterns
**Gateway Layer:**
- `gateway/app/routes/subscription.py` - Update all gateway routes with new patterns
- `gateway/app/middleware/subscription.py` - Update URL patterns
- `gateway/app/middleware/auth.py` - Update subscription tier lookup URL
- `gateway/app/middleware/read_only_mode.py` - Update subscription status check URL
**Frontend Layer:**
- `frontend/src/api/services/subscription.ts` - Update all URL patterns
### Implementation Order
1. **Backend API Reorganization** (Phase 1)
2. **Update Tenant Client** (Phase 2)
3. **Gateway Updates** (Phase 3)
4. **Frontend Updates** (Phase 4)
This comprehensive redesign creates a clean, modern subscription system with proper separation of concerns while respecting the existing service-based client architecture constraint.

View File

@@ -1,170 +0,0 @@
#!/usr/bin/env python3
"""
Test script to verify that the user endpoint changes work correctly.
This script tests the new user endpoint structure after removing /auth/me.
"""
import asyncio
import httpx
import json
from typing import Dict, Any
class UserEndpointTester:
"""Test the user endpoint changes"""
def __init__(self):
self.base_url = "http://localhost:8000" # Gateway URL
self.auth_service_url = "http://localhost:8001" # Auth service URL
self.timeout = 30.0
# Test data
self.test_user_id = "00000000-0000-0000-0000-000000000001" # Example UUID
self.test_auth_token = "test_token_12345"
async def test_auth_service_user_endpoint(self):
"""Test that the auth service user endpoint works correctly"""
print("🧪 Testing auth service user endpoint...")
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
# Test GET /api/v1/auth/users/{user_id}
url = f"{self.auth_service_url}/api/v1/auth/users/{self.test_user_id}"
headers = {
"Authorization": f"Bearer {self.test_auth_token}",
"Content-Type": "application/json"
}
print(f"📡 Requesting: GET {url}")
response = await client.get(url, headers=headers)
print(f"📤 Response status: {response.status_code}")
print(f"📦 Response headers: {dict(response.headers)}")
if response.status_code == 200:
data = response.json()
print(f"📋 Response data: {json.dumps(data, indent=2)}")
return True
elif response.status_code == 404:
print("⚠️ User not found (expected for test user)")
return True
else:
print(f"❌ Unexpected status code: {response.status_code}")
print(f"📋 Response: {response.text}")
return False
except Exception as e:
print(f"❌ Error testing auth service: {e}")
return False
async def test_gateway_user_endpoint(self):
"""Test that the gateway user endpoint works correctly"""
print("\n🧪 Testing gateway user endpoint...")
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
# Test GET /api/v1/users/{user_id}
url = f"{self.base_url}/api/v1/users/{self.test_user_id}"
headers = {
"Authorization": f"Bearer {self.test_auth_token}",
"Content-Type": "application/json"
}
print(f"📡 Requesting: GET {url}")
response = await client.get(url, headers=headers)
print(f"📤 Response status: {response.status_code}")
print(f"📦 Response headers: {dict(response.headers)}")
if response.status_code == 200:
data = response.json()
print(f"📋 Response data: {json.dumps(data, indent=2)}")
return True
elif response.status_code == 404:
print("⚠️ User not found (expected for test user)")
return True
else:
print(f"❌ Unexpected status code: {response.status_code}")
print(f"📋 Response: {response.text}")
return False
except Exception as e:
print(f"❌ Error testing gateway: {e}")
return False
async def test_auth_me_endpoint_removed(self):
"""Test that the /auth/me endpoint has been removed"""
print("\n🧪 Testing that /auth/me endpoint has been removed...")
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
# Test GET /api/v1/auth/me (should return 404)
url = f"{self.auth_service_url}/api/v1/auth/me"
headers = {
"Authorization": f"Bearer {self.test_auth_token}",
"Content-Type": "application/json"
}
print(f"📡 Requesting: GET {url}")
response = await client.get(url, headers=headers)
print(f"📤 Response status: {response.status_code}")
if response.status_code == 404:
print("✅ /auth/me endpoint correctly returns 404 (removed)")
return True
else:
print(f"❌ /auth/me endpoint still exists (status: {response.status_code})")
return False
except Exception as e:
print(f"❌ Error testing /auth/me removal: {e}")
return False
async def run_all_tests(self):
"""Run all tests"""
print("🚀 Starting user endpoint change tests...\n")
results = []
# Test 1: Auth service user endpoint
result1 = await self.test_auth_service_user_endpoint()
results.append(("Auth service user endpoint", result1))
# Test 2: Gateway user endpoint
result2 = await self.test_gateway_user_endpoint()
results.append(("Gateway user endpoint", result2))
# Test 3: /auth/me endpoint removed
result3 = await self.test_auth_me_endpoint_removed()
results.append(("/auth/me endpoint removed", result3))
# Print summary
print("\n" + "="*60)
print("📊 TEST SUMMARY")
print("="*60)
all_passed = True
for test_name, passed in results:
status = "✅ PASS" if passed else "❌ FAIL"
print(f"{status} {test_name}")
if not passed:
all_passed = False
print("="*60)
if all_passed:
print("🎉 All tests passed! User endpoint changes are working correctly.")
else:
print("⚠️ Some tests failed. Please check the implementation.")
return all_passed
if __name__ == "__main__":
tester = UserEndpointTester()
# Run tests
success = asyncio.run(tester.run_all_tests())
exit(0 if success else 1)

View File

@@ -1,149 +0,0 @@
#!/usr/bin/env python3
"""
Verification script to check that the user endpoint changes are syntactically correct.
"""
import os
import re
import sys
from pathlib import Path
def check_file_syntax(file_path: str) -> bool:
"""Check if a file has valid syntax (Python or TypeScript)"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Skip syntax check for TypeScript files
if file_path.endswith('.ts') or file_path.endswith('.tsx'):
print(f"✅ TypeScript file syntax check skipped for {file_path}")
return True
# Basic syntax check for Python files
compile(content, file_path, 'exec')
return True
except SyntaxError as e:
print(f"❌ Syntax error in {file_path}: {e}")
return False
except Exception as e:
print(f"❌ Error reading {file_path}: {e}")
return False
def check_auth_me_removed(file_path: str) -> bool:
"""Check that /auth/me endpoint has been removed"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Look for the /auth/me endpoint definition
if '@router.get("/me"' in content or 'def get_current_user(' in content:
print(f"❌ /auth/me endpoint still exists in {file_path}")
return False
return True
except Exception as e:
print(f"❌ Error checking {file_path}: {e}")
return False
def check_user_service_updated(file_path: str) -> bool:
"""Check that UserService has been updated to use user ID"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Check for the new implementation
if 'useAuthStore.getState()' in content and 'userId = authStore.user?.id' in content:
print(f"✅ UserService correctly updated in {file_path}")
return True
else:
print(f"❌ UserService not properly updated in {file_path}")
return False
except Exception as e:
print(f"❌ Error checking {file_path}: {e}")
return False
def check_api_client_updated(file_path: str) -> bool:
"""Check that API client has been updated"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Check that /auth/me is no longer in noTenantEndpoints (but allow /auth/me/onboarding)
lines = content.split('\n')
found_auth_me_alone = False
for line in lines:
# Look for /auth/me but not /auth/me/onboarding
if line.strip() == "'/auth/me'," or line.strip().endswith("/auth/me',"):
found_auth_me_alone = True
break
if found_auth_me_alone:
print(f"❌ /auth/me still in noTenantEndpoints in {file_path}")
return False
else:
print(f"✅ API client correctly updated in {file_path}")
return True
except Exception as e:
print(f"❌ Error checking {file_path}: {e}")
return False
def main():
"""Main verification function"""
print("🔍 Verifying user endpoint changes...")
print("=" * 60)
# Files to check
files_to_check = [
{
'path': '/Users/urtzialfaro/Documents/bakery-ia/services/auth/app/api/auth_operations.py',
'checks': [check_file_syntax, check_auth_me_removed]
},
{
'path': '/Users/urtzialfaro/Documents/bakery-ia/frontend/src/api/services/user.ts',
'checks': [check_file_syntax, check_user_service_updated]
},
{
'path': '/Users/urtzialfaro/Documents/bakery-ia/frontend/src/api/client/apiClient.ts',
'checks': [check_file_syntax, check_api_client_updated]
}
]
all_passed = True
for file_info in files_to_check:
file_path = file_info['path']
checks = file_info['checks']
print(f"\n📁 Checking {file_path}...")
for check in checks:
if not check(file_path):
all_passed = False
print("\n" + "=" * 60)
if all_passed:
print("🎉 All verification checks passed!")
print("\n✅ Changes summary:")
print(" • Removed /auth/me endpoint from auth service")
print(" • Updated UserService to use /users/{user_id}")
print(" • Updated API client to remove /auth/me from noTenantEndpoints")
print(" • All files have valid syntax")
else:
print("❌ Some verification checks failed!")
print("\nPlease review the errors above and fix them.")
print("=" * 60)
return all_passed
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)