docs: add feature gating system documentation
- Add comprehensive feature-gating-system.md documenting: - Database models (Feature, VendorFeatureOverride) - 30 features across 8 categories with tier requirements - FeatureService and UsageService APIs - Backend enforcement (decorator, dependency patterns) - Frontend integration (Alpine stores, Jinja macros) - Admin and vendor API endpoints - Update subscription-workflow-plan.md with completion status - Add feature-gating-system.md to mkdocs.yml navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
377
docs/implementation/feature-gating-system.md
Normal file
377
docs/implementation/feature-gating-system.md
Normal file
@@ -0,0 +1,377 @@
|
||||
# Feature Gating System
|
||||
|
||||
## Overview
|
||||
|
||||
The feature gating system provides tier-based access control for platform features. It allows restricting functionality based on vendor subscription tiers (Essential, Professional, Business, Enterprise) with contextual upgrade prompts when features are locked.
|
||||
|
||||
**Implemented:** December 31, 2025
|
||||
|
||||
## Architecture
|
||||
|
||||
### Database Models
|
||||
|
||||
Located in `models/database/feature.py`:
|
||||
|
||||
| Model | Purpose |
|
||||
|-------|---------|
|
||||
| `Feature` | Feature definitions with tier requirements |
|
||||
| `VendorFeatureOverride` | Per-vendor feature overrides (enable/disable) |
|
||||
|
||||
### Feature Model Structure
|
||||
|
||||
```python
|
||||
class Feature(Base):
|
||||
__tablename__ = "features"
|
||||
|
||||
id: int # Primary key
|
||||
code: str # Unique feature code (e.g., "analytics_dashboard")
|
||||
name: str # Display name
|
||||
description: str # User-facing description
|
||||
category: str # Feature category
|
||||
minimum_tier_code: str # Minimum tier required (essential/professional/business/enterprise)
|
||||
minimum_tier_order: int # Tier order for comparison (1-4)
|
||||
is_active: bool # Whether feature is available
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
```
|
||||
|
||||
### Tier Ordering
|
||||
|
||||
| Tier | Order | Code |
|
||||
|------|-------|------|
|
||||
| Essential | 1 | `essential` |
|
||||
| Professional | 2 | `professional` |
|
||||
| Business | 3 | `business` |
|
||||
| Enterprise | 4 | `enterprise` |
|
||||
|
||||
## Feature Categories
|
||||
|
||||
30 features organized into 8 categories:
|
||||
|
||||
### 1. Analytics
|
||||
| Feature Code | Name | Min Tier |
|
||||
|-------------|------|----------|
|
||||
| `basic_analytics` | Basic Analytics | Essential |
|
||||
| `analytics_dashboard` | Analytics Dashboard | Professional |
|
||||
| `advanced_analytics` | Advanced Analytics | Business |
|
||||
| `custom_reports` | Custom Reports | Enterprise |
|
||||
|
||||
### 2. Product Management
|
||||
| Feature Code | Name | Min Tier |
|
||||
|-------------|------|----------|
|
||||
| `basic_products` | Product Management | Essential |
|
||||
| `bulk_product_edit` | Bulk Product Edit | Professional |
|
||||
| `product_variants` | Product Variants | Professional |
|
||||
| `product_bundles` | Product Bundles | Business |
|
||||
| `inventory_alerts` | Inventory Alerts | Professional |
|
||||
|
||||
### 3. Order Management
|
||||
| Feature Code | Name | Min Tier |
|
||||
|-------------|------|----------|
|
||||
| `basic_orders` | Order Management | Essential |
|
||||
| `order_automation` | Order Automation | Professional |
|
||||
| `advanced_fulfillment` | Advanced Fulfillment | Business |
|
||||
| `multi_warehouse` | Multi-Warehouse | Enterprise |
|
||||
|
||||
### 4. Marketing
|
||||
| Feature Code | Name | Min Tier |
|
||||
|-------------|------|----------|
|
||||
| `discount_codes` | Discount Codes | Professional |
|
||||
| `abandoned_cart` | Abandoned Cart Recovery | Business |
|
||||
| `email_marketing` | Email Marketing | Business |
|
||||
| `loyalty_program` | Loyalty Program | Enterprise |
|
||||
|
||||
### 5. Support
|
||||
| Feature Code | Name | Min Tier |
|
||||
|-------------|------|----------|
|
||||
| `basic_support` | Email Support | Essential |
|
||||
| `priority_support` | Priority Support | Professional |
|
||||
| `phone_support` | Phone Support | Business |
|
||||
| `dedicated_manager` | Dedicated Account Manager | Enterprise |
|
||||
|
||||
### 6. Integration
|
||||
| Feature Code | Name | Min Tier |
|
||||
|-------------|------|----------|
|
||||
| `basic_api` | Basic API Access | Professional |
|
||||
| `advanced_api` | Advanced API Access | Business |
|
||||
| `webhooks` | Webhooks | Business |
|
||||
| `custom_integrations` | Custom Integrations | Enterprise |
|
||||
|
||||
### 7. Branding
|
||||
| Feature Code | Name | Min Tier |
|
||||
|-------------|------|----------|
|
||||
| `basic_theme` | Theme Customization | Essential |
|
||||
| `custom_domain` | Custom Domain | Professional |
|
||||
| `white_label` | White Label | Enterprise |
|
||||
| `custom_checkout` | Custom Checkout | Enterprise |
|
||||
|
||||
### 8. Team
|
||||
| Feature Code | Name | Min Tier |
|
||||
|-------------|------|----------|
|
||||
| `team_management` | Team Management | Professional |
|
||||
| `role_permissions` | Role Permissions | Business |
|
||||
| `audit_logs` | Audit Logs | Business |
|
||||
|
||||
## Services
|
||||
|
||||
### FeatureService
|
||||
|
||||
Located in `app/services/feature_service.py`:
|
||||
|
||||
```python
|
||||
class FeatureService:
|
||||
"""Service for managing tier-based feature access."""
|
||||
|
||||
# In-memory caching (refreshed every 5 minutes)
|
||||
_feature_cache: dict[str, Feature] = {}
|
||||
_cache_timestamp: datetime | None = None
|
||||
CACHE_TTL_SECONDS = 300
|
||||
|
||||
def has_feature(self, db: Session, vendor_id: int, feature_code: str) -> bool:
|
||||
"""Check if vendor has access to a feature."""
|
||||
|
||||
def get_available_features(self, db: Session, vendor_id: int) -> list[str]:
|
||||
"""Get list of feature codes available to vendor."""
|
||||
|
||||
def get_all_features_with_status(self, db: Session, vendor_id: int) -> list[dict]:
|
||||
"""Get all features with availability status for vendor."""
|
||||
|
||||
def get_feature_info(self, db: Session, feature_code: str) -> dict | None:
|
||||
"""Get full feature information including tier requirements."""
|
||||
```
|
||||
|
||||
### UsageService
|
||||
|
||||
Located in `app/services/usage_service.py`:
|
||||
|
||||
```python
|
||||
class UsageService:
|
||||
"""Service for tracking and managing vendor usage against tier limits."""
|
||||
|
||||
def get_usage_summary(self, db: Session, vendor_id: int) -> dict:
|
||||
"""Get comprehensive usage summary with limits and upgrade info."""
|
||||
|
||||
def check_limit(self, db: Session, vendor_id: int, limit_type: str) -> dict:
|
||||
"""Check specific limit with detailed info."""
|
||||
|
||||
def get_upgrade_info(self, db: Session, vendor_id: int) -> dict:
|
||||
"""Get upgrade recommendations based on current usage."""
|
||||
```
|
||||
|
||||
## Backend Enforcement
|
||||
|
||||
### Decorator Pattern
|
||||
|
||||
```python
|
||||
from app.core.feature_gate import require_feature
|
||||
|
||||
@router.get("/analytics/advanced")
|
||||
@require_feature("advanced_analytics")
|
||||
async def get_advanced_analytics(
|
||||
db: Session = Depends(get_db),
|
||||
vendor_id: int = Depends(get_current_vendor_id)
|
||||
):
|
||||
# Only accessible if vendor has advanced_analytics feature
|
||||
pass
|
||||
```
|
||||
|
||||
### Dependency Pattern
|
||||
|
||||
```python
|
||||
from app.core.feature_gate import RequireFeature
|
||||
|
||||
@router.get("/marketing/loyalty")
|
||||
async def get_loyalty_program(
|
||||
db: Session = Depends(get_db),
|
||||
_: None = Depends(RequireFeature("loyalty_program"))
|
||||
):
|
||||
# Only accessible if vendor has loyalty_program feature
|
||||
pass
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
|
||||
When a feature is not available, `FeatureNotAvailableException` is raised:
|
||||
|
||||
```python
|
||||
class FeatureNotAvailableException(Exception):
|
||||
def __init__(self, feature_code: str, required_tier: str):
|
||||
self.feature_code = feature_code
|
||||
self.required_tier = required_tier
|
||||
super().__init__(f"Feature '{feature_code}' requires {required_tier} tier")
|
||||
```
|
||||
|
||||
HTTP Response (403):
|
||||
```json
|
||||
{
|
||||
"detail": "Feature 'advanced_analytics' requires Professional tier or higher",
|
||||
"feature_code": "advanced_analytics",
|
||||
"required_tier": "Professional",
|
||||
"upgrade_url": "/vendor/wizamart/billing"
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Vendor Features API
|
||||
|
||||
Base: `/api/v1/vendor/features`
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/features/available` | GET | List available feature codes |
|
||||
| `/features` | GET | All features with availability status |
|
||||
| `/features/{code}` | GET | Single feature info |
|
||||
| `/features/{code}/check` | GET | Quick availability check |
|
||||
|
||||
### Vendor Usage API
|
||||
|
||||
Base: `/api/v1/vendor/usage`
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/usage` | GET | Full usage summary with limits |
|
||||
| `/usage/check/{limit_type}` | GET | Check specific limit (orders/products/team_members) |
|
||||
| `/usage/upgrade-info` | GET | Upgrade recommendations |
|
||||
|
||||
### Admin Features API
|
||||
|
||||
Base: `/api/v1/admin/features`
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/features` | GET | List all features |
|
||||
| `/features/{id}` | GET | Get feature details |
|
||||
| `/features/{id}` | PUT | Update feature |
|
||||
| `/features/{id}/toggle` | POST | Toggle feature active status |
|
||||
| `/features/vendors/{vendor_id}/overrides` | GET | Get vendor overrides |
|
||||
| `/features/vendors/{vendor_id}/overrides` | POST | Create override |
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### Alpine.js Feature Store
|
||||
|
||||
Located in `static/shared/js/feature-store.js`:
|
||||
|
||||
```javascript
|
||||
// Usage in templates
|
||||
$store.features.has('analytics_dashboard') // Check feature
|
||||
$store.features.loaded // Loading state
|
||||
$store.features.getFeature('advanced_api') // Get feature details
|
||||
```
|
||||
|
||||
### Alpine.js Upgrade Store
|
||||
|
||||
Located in `static/shared/js/upgrade-prompts.js`:
|
||||
|
||||
```javascript
|
||||
// Usage in templates
|
||||
$store.upgrade.shouldShowLimitWarning('orders')
|
||||
$store.upgrade.getUsageString('products')
|
||||
$store.upgrade.hasUpgradeRecommendation
|
||||
```
|
||||
|
||||
### Jinja2 Macros
|
||||
|
||||
Located in `app/templates/shared/macros/feature_gate.html`:
|
||||
|
||||
#### Feature Gate Container
|
||||
```jinja2
|
||||
{% from "shared/macros/feature_gate.html" import feature_gate %}
|
||||
|
||||
{% call feature_gate("analytics_dashboard") %}
|
||||
<div>Analytics content here - only visible if feature available</div>
|
||||
{% endcall %}
|
||||
```
|
||||
|
||||
#### Feature Locked Card
|
||||
```jinja2
|
||||
{% from "shared/macros/feature_gate.html" import feature_locked %}
|
||||
|
||||
{{ feature_locked("advanced_analytics", "Advanced Analytics", "Get deeper insights") }}
|
||||
```
|
||||
|
||||
#### Upgrade Banner
|
||||
```jinja2
|
||||
{% from "shared/macros/feature_gate.html" import upgrade_banner %}
|
||||
|
||||
{{ upgrade_banner("custom_domain") }}
|
||||
```
|
||||
|
||||
#### Usage Limit Warning
|
||||
```jinja2
|
||||
{% from "shared/macros/feature_gate.html" import limit_warning %}
|
||||
|
||||
{{ limit_warning("orders") }} {# Shows warning when approaching limit #}
|
||||
```
|
||||
|
||||
#### Usage Progress Bar
|
||||
```jinja2
|
||||
{% from "shared/macros/feature_gate.html" import usage_bar %}
|
||||
|
||||
{{ usage_bar("products", "Products") }}
|
||||
```
|
||||
|
||||
#### Tier Badge
|
||||
```jinja2
|
||||
{% from "shared/macros/feature_gate.html" import tier_badge %}
|
||||
|
||||
{{ tier_badge() }} {# Shows current tier as colored badge #}
|
||||
```
|
||||
|
||||
## Vendor Dashboard Integration
|
||||
|
||||
The vendor dashboard (`/vendor/{code}/dashboard`) now includes:
|
||||
|
||||
1. **Tier Badge**: Shows current subscription tier in header
|
||||
2. **Usage Bars**: Visual progress bars for orders, products, team members
|
||||
3. **Upgrade Prompts**: Contextual upgrade recommendations when approaching limits
|
||||
4. **Feature Gates**: Locked sections for premium features
|
||||
|
||||
## Admin Features Page
|
||||
|
||||
Located at `/admin/features`:
|
||||
|
||||
- View all 30 features in categorized table
|
||||
- Toggle features on/off globally
|
||||
- Filter by category
|
||||
- Search by name/code
|
||||
- View tier requirements
|
||||
|
||||
## Migration
|
||||
|
||||
The features are seeded via Alembic migration:
|
||||
|
||||
```
|
||||
alembic/versions/n2c3d4e5f6a7_add_features_table.py
|
||||
```
|
||||
|
||||
This creates:
|
||||
- `features` table with 30 default features
|
||||
- `vendor_feature_overrides` table for per-vendor exceptions
|
||||
|
||||
## Testing
|
||||
|
||||
Unit tests located in:
|
||||
- `tests/unit/services/test_feature_service.py`
|
||||
- `tests/unit/services/test_usage_service.py`
|
||||
|
||||
Run tests:
|
||||
```bash
|
||||
pytest tests/unit/services/test_feature_service.py -v
|
||||
pytest tests/unit/services/test_usage_service.py -v
|
||||
```
|
||||
|
||||
## Architecture Compliance
|
||||
|
||||
All JavaScript files follow architecture rules:
|
||||
- JS-003: Alpine components use `vendor*` naming convention
|
||||
- JS-005: Init guards prevent duplicate initialization
|
||||
- JS-006: Async operations have try/catch error handling
|
||||
- JS-008: API calls use `apiClient` (not raw `fetch()`)
|
||||
- JS-009: Notifications use `Utils.showToast()`
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Subscription Billing](../features/subscription-billing.md) - Core subscription system
|
||||
- [Subscription Workflow Plan](./subscription-workflow-plan.md) - Implementation roadmap
|
||||
Reference in New Issue
Block a user