feat: add logging, marketplace, and admin enhancements

Database & Migrations:
- Add application_logs table migration for hybrid cloud logging
- Add companies table migration and restructure vendor relationships

Logging System:
- Implement hybrid logging system (database + file)
- Add log_service for centralized log management
- Create admin logs page with filtering and viewing capabilities
- Add init_log_settings.py script for log configuration
- Enhance core logging with database integration

Marketplace Integration:
- Add marketplace admin page with product management
- Create marketplace vendor page with product listings
- Implement marketplace.js for both admin and vendor interfaces
- Add marketplace integration documentation

Admin Enhancements:
- Add imports management page and functionality
- Create settings page for admin configuration
- Add vendor themes management page
- Enhance vendor detail and edit pages
- Improve code quality dashboard and violation details
- Add logs viewing and management
- Update icons guide and shared icon system

Architecture & Documentation:
- Document frontend structure and component architecture
- Document models structure and relationships
- Add vendor-in-token architecture documentation
- Add vendor RBAC (role-based access control) documentation
- Document marketplace integration patterns
- Update architecture patterns documentation

Infrastructure:
- Add platform static files structure (css, img, js)
- Move architecture_scan.py to proper models location
- Update model imports and registrations
- Enhance exception handling
- Update dependency injection patterns

UI/UX:
- Improve vendor edit interface
- Update admin user interface
- Enhance page templates documentation
- Add vendor marketplace interface
This commit is contained in:
2025-12-01 21:51:07 +01:00
parent 915734e9b4
commit cc74970223
56 changed files with 8440 additions and 202 deletions

View File

@@ -0,0 +1,500 @@
# Vendor-in-Token Architecture
## Overview
This document describes the vendor-in-token authentication architecture used for vendor API endpoints. This architecture embeds vendor context directly into JWT tokens, eliminating the need for URL-based vendor detection and enabling clean, RESTful API endpoints.
## The Problem: URL-Based Vendor Detection
### Old Pattern (Deprecated)
```python
# ❌ DEPRECATED: URL-based vendor detection
@router.get("/{product_id}")
def get_product(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()), # ❌ Don't use
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
product = product_service.get_product(db, vendor.id, product_id)
return product
```
### Issues with URL-Based Detection
1. **Inconsistent API Routes**
- Page routes: `/vendor/{vendor_code}/dashboard` (has vendor in URL)
- API routes: `/api/v1/vendor/products` (no vendor in URL)
- `require_vendor_context()` only works when vendor is in the URL path
2. **404 Errors on API Endpoints**
- API calls to `/api/v1/vendor/products` would return 404
- The dependency expected vendor code in URL but API routes don't have it
- Breaking RESTful API design principles
3. **Architecture Violation**
- Mixed concerns: URL structure determining business logic
- Tight coupling between routing and vendor context
- Harder to test and maintain
## The Solution: Vendor-in-Token
### Architecture Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ Vendor Login Flow │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 1. Authenticate user credentials │
│ 2. Validate vendor membership │
│ 3. Create JWT with vendor context: │
│ { │
│ "sub": "user_id", │
│ "username": "john.doe", │
│ "vendor_id": 123, ← Vendor context in token │
│ "vendor_code": "WIZAMART", ← Vendor code in token │
│ "vendor_role": "Owner" ← Vendor role in token │
│ } │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 4. Set dual token storage: │
│ - HTTP-only cookie (path=/vendor) for page navigation │
│ - Response body for localStorage (API calls) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 5. Subsequent API requests include vendor context │
│ Authorization: Bearer <token-with-vendor-context> │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 6. get_current_vendor_api() extracts vendor from token: │
│ - current_user.token_vendor_id │
│ - current_user.token_vendor_code │
│ - current_user.token_vendor_role │
│ 7. Validates user still has access to vendor │
└─────────────────────────────────────────────────────────────────┘
```
### Implementation Components
#### 1. Token Creation (middleware/auth.py)
```python
def create_access_token(
self,
user: User,
vendor_id: int | None = None,
vendor_code: str | None = None,
vendor_role: str | None = None,
) -> dict[str, Any]:
"""Create JWT with optional vendor context."""
payload = {
"sub": str(user.id),
"username": user.username,
"email": user.email,
"role": user.role,
"exp": expire,
"iat": datetime.now(UTC),
}
# Include vendor information in token if provided
if vendor_id is not None:
payload["vendor_id"] = vendor_id
if vendor_code is not None:
payload["vendor_code"] = vendor_code
if vendor_role is not None:
payload["vendor_role"] = vendor_role
return {
"access_token": jwt.encode(payload, self.secret_key, algorithm=self.algorithm),
"token_type": "bearer",
"expires_in": self.access_token_expire_minutes * 60,
}
```
#### 2. Vendor Login (app/api/v1/vendor/auth.py)
```python
@router.post("/login", response_model=VendorLoginResponse)
def vendor_login(
user_credentials: UserLogin,
response: Response,
db: Session = Depends(get_db),
):
"""
Vendor team member login.
Creates vendor-scoped JWT token with vendor context embedded.
"""
# Authenticate user and determine vendor
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
user = login_result["user"]
# Determine vendor and role
vendor = determine_vendor(db, user) # Your vendor detection logic
vendor_role = determine_role(db, user, vendor) # Your role detection logic
# Create vendor-scoped access token
token_data = auth_service.auth_manager.create_access_token(
user=user,
vendor_id=vendor.id,
vendor_code=vendor.vendor_code,
vendor_role=vendor_role,
)
# Set cookie and return token
response.set_cookie(
key="vendor_token",
value=token_data["access_token"],
httponly=True,
path="/vendor", # Restricted to vendor routes
)
return VendorLoginResponse(**token_data, user=user, vendor=vendor)
```
#### 3. Token Verification (app/api/deps.py)
```python
def get_current_vendor_api(
authorization: str | None = Header(None, alias="Authorization"),
db: Session = Depends(get_db),
) -> User:
"""
Get current vendor API user from Authorization header.
Extracts vendor context from JWT token and validates access.
"""
if not authorization or not authorization.startswith("Bearer "):
raise AuthenticationException("Authorization header required for API calls")
token = authorization.replace("Bearer ", "")
user = auth_service.auth_manager.get_current_user(token, db)
# Validate vendor access if token is vendor-scoped
if hasattr(user, "token_vendor_id"):
vendor_id = user.token_vendor_id
# Verify user still has access to this vendor
if not user.is_member_of(vendor_id):
raise InsufficientPermissionsException(
"Access to vendor has been revoked. Please login again."
)
return user
```
#### 4. Endpoint Usage (app/api/v1/vendor/products.py)
```python
@router.get("", response_model=ProductListResponse)
def get_vendor_products(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
current_user: User = Depends(get_current_vendor_api), # ✅ Only need this
db: Session = Depends(get_db),
):
"""
Get all products in vendor catalog.
Vendor is determined from JWT token (vendor_id claim).
"""
# Extract vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise HTTPException(
status_code=400,
detail="Token missing vendor information. Please login again.",
)
vendor_id = current_user.token_vendor_id
# Use vendor_id from token for business logic
products, total = product_service.get_vendor_products(
db=db,
vendor_id=vendor_id,
skip=skip,
limit=limit,
)
return ProductListResponse(products=products, total=total)
```
## Migration Guide
### Step 1: Identify Endpoints Using require_vendor_context()
Search for all occurrences:
```bash
grep -r "require_vendor_context" app/api/v1/vendor/
```
### Step 2: Update Endpoint Signature
**Before:**
```python
@router.get("/{product_id}")
def get_product(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()), # ❌ Remove this
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
```
**After:**
```python
@router.get("/{product_id}")
def get_product(
product_id: int,
current_user: User = Depends(get_current_vendor_api), # ✅ Only need this
db: Session = Depends(get_db),
):
```
### Step 3: Extract Vendor from Token
**Before:**
```python
product = product_service.get_product(db, vendor.id, product_id)
```
**After:**
```python
from fastapi import HTTPException
# Extract vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise HTTPException(
status_code=400,
detail="Token missing vendor information. Please login again.",
)
vendor_id = current_user.token_vendor_id
# Use vendor_id from token
product = product_service.get_product(db, vendor_id, product_id)
```
### Step 4: Update Logging References
**Before:**
```python
logger.info(f"Product updated for vendor {vendor.vendor_code}")
```
**After:**
```python
logger.info(f"Product updated for vendor {current_user.token_vendor_code}")
```
### Complete Migration Example
**Before (URL-based vendor detection):**
```python
@router.put("/{product_id}", response_model=ProductResponse)
def update_product(
product_id: int,
product_data: ProductUpdate,
vendor: Vendor = Depends(require_vendor_context()), # ❌
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Update product in vendor catalog."""
product = product_service.update_product(
db=db,
vendor_id=vendor.id, # ❌ From URL
product_id=product_id,
product_update=product_data
)
logger.info(
f"Product {product_id} updated by {current_user.username} "
f"for vendor {vendor.vendor_code}" # ❌ From URL
)
return ProductResponse.model_validate(product)
```
**After (Token-based vendor context):**
```python
@router.put("/{product_id}", response_model=ProductResponse)
def update_product(
product_id: int,
product_data: ProductUpdate,
current_user: User = Depends(get_current_vendor_api), # ✅ Only dependency
db: Session = Depends(get_db),
):
"""Update product in vendor catalog."""
from fastapi import HTTPException
# Extract vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise HTTPException(
status_code=400,
detail="Token missing vendor information. Please login again.",
)
vendor_id = current_user.token_vendor_id # ✅ From token
product = product_service.update_product(
db=db,
vendor_id=vendor_id, # ✅ From token
product_id=product_id,
product_update=product_data
)
logger.info(
f"Product {product_id} updated by {current_user.username} "
f"for vendor {current_user.token_vendor_code}" # ✅ From token
)
return ProductResponse.model_validate(product)
```
## Files to Migrate
Current files still using `require_vendor_context()`:
- `app/api/v1/vendor/customers.py`
- `app/api/v1/vendor/notifications.py`
- `app/api/v1/vendor/media.py`
- `app/api/v1/vendor/marketplace.py`
- `app/api/v1/vendor/inventory.py`
- `app/api/v1/vendor/settings.py`
- `app/api/v1/vendor/analytics.py`
- `app/api/v1/vendor/payments.py`
- `app/api/v1/vendor/profile.py`
## Benefits of Vendor-in-Token
### 1. Clean RESTful APIs
```
✅ /api/v1/vendor/products
✅ /api/v1/vendor/orders
✅ /api/v1/vendor/customers
❌ /api/v1/vendor/{vendor_code}/products (unnecessary vendor in URL)
```
### 2. Security
- Vendor context cryptographically signed in JWT
- Cannot be tampered with by client
- Automatic validation on every request
- Token revocation possible via database checks
### 3. Consistency
- Same authentication mechanism for all vendor API endpoints
- No confusion between page routes and API routes
- Single source of truth (the token)
### 4. Performance
- No database lookup for vendor context on every request
- Vendor information already in token payload
- Optional validation for revoked access
### 5. Maintainability
- Simpler endpoint signatures
- Less boilerplate code
- Easier to test
- Follows architecture rule API-002 (no DB queries in endpoints)
## Security Considerations
### Token Validation
The token vendor context is validated on every request:
1. JWT signature verification (ensures token not tampered with)
2. Token expiration check (typically 30 minutes)
3. Optional: Verify user still member of vendor (database check)
### Access Revocation
If a user's vendor access is revoked:
1. Existing tokens remain valid until expiration
2. `get_current_vendor_api()` performs optional database check
3. User forced to re-login after token expires
4. New login will fail if access revoked
### Token Refresh
Tokens should be refreshed periodically:
- Default: 30 minutes expiration
- Refresh before expiration for seamless UX
- New login creates new token with current vendor membership
## Testing
### Unit Tests
```python
def test_vendor_in_token():
"""Test vendor context in JWT token."""
# Create token with vendor context
token_data = auth_manager.create_access_token(
user=user,
vendor_id=123,
vendor_code="WIZAMART",
vendor_role="Owner",
)
# Verify token contains vendor data
payload = jwt.decode(token_data["access_token"], secret_key)
assert payload["vendor_id"] == 123
assert payload["vendor_code"] == "WIZAMART"
assert payload["vendor_role"] == "Owner"
def test_api_endpoint_uses_token_vendor():
"""Test API endpoint extracts vendor from token."""
response = client.get(
"/api/v1/vendor/products",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
# Verify products are filtered by token vendor_id
```
### Integration Tests
```python
def test_vendor_login_and_api_access():
"""Test full vendor login and API access flow."""
# Login as vendor user
response = client.post("/api/v1/vendor/auth/login", json={
"username": "john.doe",
"password": "password123"
})
assert response.status_code == 200
token = response.json()["access_token"]
# Access vendor API with token
response = client.get(
"/api/v1/vendor/products",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
# Verify vendor context from token
products = response.json()["products"]
# All products should belong to token vendor
```
## Architecture Rules
See `docs/architecture/rules/API-VND-001.md` for the formal architecture rule enforcing this pattern.
## Related Documentation
- [Vendor RBAC System](./vendor-rbac.md) - Role-based access control for vendors
- [Vendor Authentication](./vendor-authentication.md) - Complete authentication guide
- [Architecture Rules](../architecture/rules/) - All architecture rules
- [API Design Guidelines](../architecture/api-design.md) - RESTful API patterns
## Summary
The vendor-in-token architecture:
- ✅ Embeds vendor context in JWT tokens
- ✅ Eliminates URL-based vendor detection
- ✅ Enables clean RESTful API endpoints
- ✅ Improves security and performance
- ✅ Simplifies endpoint implementation
- ✅ Follows architecture best practices
**Migration Status:** In progress - 9 endpoint files remaining to migrate

678
docs/backend/vendor-rbac.md Normal file
View File

@@ -0,0 +1,678 @@
# Vendor RBAC System - Complete Guide
## Overview
The vendor dashboard implements a **Role-Based Access Control (RBAC)** system that distinguishes between **Owners** and **Team Members**, with granular permissions for team members.
---
## User Types
### 1. Vendor Owner
**Who:** The user who created the vendor account.
**Characteristics:**
- Has **ALL permissions** automatically (no role needed)
- Cannot be removed or have permissions restricted
- Can invite team members
- Can create and manage roles
- Identified by `VendorUser.user_type = "owner"`
- Linked via `Vendor.owner_user_id → User.id`
**Database:**
```python
# VendorUser record for owner
{
"vendor_id": 1,
"user_id": 5,
"user_type": "owner", # ✓ Owner
"role_id": None, # No role needed
"is_active": True
}
```
**Permissions:**
-**All 75 permissions** (complete access)
- See full list below
---
### 2. Team Members
**Who:** Users invited by the vendor owner to help manage the vendor.
**Characteristics:**
- Have **limited permissions** based on assigned role
- Must be invited via email
- Invitation must be accepted before activation
- Can be assigned one of the pre-defined roles or custom role
- Identified by `VendorUser.user_type = "member"`
- Permissions come from `VendorUser.role_id → Role.permissions`
**Database:**
```python
# VendorUser record for team member
{
"vendor_id": 1,
"user_id": 7,
"user_type": "member", # ✓ Team member
"role_id": 3, # ✓ Role required
"is_active": True,
"invitation_token": None, # Accepted
"invitation_accepted_at": "2024-11-15 10:30:00"
}
# Role record
{
"id": 3,
"vendor_id": 1,
"name": "Manager",
"permissions": [
"dashboard.view",
"products.view",
"products.create",
"products.edit",
"orders.view",
...
]
}
```
**Permissions:**
- 🔒 **Limited** based on assigned role
- Can have between 0 and 75 permissions
- Common roles: Manager, Staff, Support, Viewer, Marketing
---
## Permission System
### All Available Permissions (75 total)
```python
class VendorPermissions(str, Enum):
# Dashboard (1)
DASHBOARD_VIEW = "dashboard.view"
# Products (6)
PRODUCTS_VIEW = "products.view"
PRODUCTS_CREATE = "products.create"
PRODUCTS_EDIT = "products.edit"
PRODUCTS_DELETE = "products.delete"
PRODUCTS_IMPORT = "products.import"
PRODUCTS_EXPORT = "products.export"
# Stock/Inventory (3)
STOCK_VIEW = "stock.view"
STOCK_EDIT = "stock.edit"
STOCK_TRANSFER = "stock.transfer"
# Orders (4)
ORDERS_VIEW = "orders.view"
ORDERS_EDIT = "orders.edit"
ORDERS_CANCEL = "orders.cancel"
ORDERS_REFUND = "orders.refund"
# Customers (4)
CUSTOMERS_VIEW = "customers.view"
CUSTOMERS_EDIT = "customers.edit"
CUSTOMERS_DELETE = "customers.delete"
CUSTOMERS_EXPORT = "customers.export"
# Marketing (3)
MARKETING_VIEW = "marketing.view"
MARKETING_CREATE = "marketing.create"
MARKETING_SEND = "marketing.send"
# Reports (3)
REPORTS_VIEW = "reports.view"
REPORTS_FINANCIAL = "reports.financial"
REPORTS_EXPORT = "reports.export"
# Settings (4)
SETTINGS_VIEW = "settings.view"
SETTINGS_EDIT = "settings.edit"
SETTINGS_THEME = "settings.theme"
SETTINGS_DOMAINS = "settings.domains"
# Team Management (4)
TEAM_VIEW = "team.view"
TEAM_INVITE = "team.invite"
TEAM_EDIT = "team.edit"
TEAM_REMOVE = "team.remove"
# Marketplace Imports (3)
IMPORTS_VIEW = "imports.view"
IMPORTS_CREATE = "imports.create"
IMPORTS_CANCEL = "imports.cancel"
```
---
## Pre-Defined Roles
### 1. Owner (All 75 permissions)
**Use case:** Vendor owner (automatically assigned)
- ✅ Full access to everything
- ✅ Cannot be restricted
- ✅ No role record needed (permissions checked differently)
---
### 2. Manager (43 permissions)
**Use case:** Senior staff who manage most operations
**Has access to:**
- ✅ Dashboard, Products (all), Stock (all)
- ✅ Orders (all), Customers (view, edit, export)
- ✅ Marketing (all), Reports (all including financial)
- ✅ Settings (view, theme)
- ✅ Imports (all)
**Does NOT have:**
-`customers.delete` - Cannot delete customers
-`settings.edit` - Cannot change core settings
-`settings.domains` - Cannot manage domains
-`team.*` - Cannot manage team members
---
### 3. Staff (10 permissions)
**Use case:** Daily operations staff
**Has access to:**
- ✅ Dashboard view
- ✅ Products (view, create, edit)
- ✅ Stock (view, edit)
- ✅ Orders (view, edit)
- ✅ Customers (view, edit)
**Does NOT have:**
- ❌ Delete anything
- ❌ Import/export
- ❌ Marketing
- ❌ Financial reports
- ❌ Settings
- ❌ Team management
---
### 4. Support (6 permissions)
**Use case:** Customer support team
**Has access to:**
- ✅ Dashboard view
- ✅ Products (view only)
- ✅ Orders (view, edit)
- ✅ Customers (view, edit)
**Does NOT have:**
- ❌ Create/delete products
- ❌ Stock management
- ❌ Marketing
- ❌ Reports
- ❌ Settings
- ❌ Team management
---
### 5. Viewer (6 permissions)
**Use case:** Read-only access for reporting/audit
**Has access to:**
- ✅ Dashboard (view)
- ✅ Products (view)
- ✅ Stock (view)
- ✅ Orders (view)
- ✅ Customers (view)
- ✅ Reports (view)
**Does NOT have:**
- ❌ Edit anything
- ❌ Create/delete anything
- ❌ Marketing
- ❌ Financial reports
- ❌ Settings
- ❌ Team management
---
### 6. Marketing (7 permissions)
**Use case:** Marketing team focused on campaigns
**Has access to:**
- ✅ Dashboard (view)
- ✅ Customers (view, export)
- ✅ Marketing (all)
- ✅ Reports (view)
**Does NOT have:**
- ❌ Products management
- ❌ Orders management
- ❌ Stock management
- ❌ Financial reports
- ❌ Settings
- ❌ Team management
---
## Permission Checking Logic
### How Permissions Are Checked
```python
# In User model (models/database/user.py)
def has_vendor_permission(self, vendor_id: int, permission: str) -> bool:
"""Check if user has a specific permission in a vendor."""
# Step 1: Check if user is owner
if self.is_owner_of(vendor_id):
return True # ✅ Owners have ALL permissions
# Step 2: Check team member permissions
for vm in self.vendor_memberships:
if vm.vendor_id == vendor_id and vm.is_active:
if vm.role and permission in vm.role.permissions:
return True # ✅ Permission found in role
# No permission found
return False
```
### Permission Checking Flow
```
Request → Middleware → Extract vendor from URL
Check user authentication
Check if user is owner
├── YES → ✅ Allow (all permissions)
└── NO ↓
Check if user is team member
├── NO → ❌ Deny
└── YES ↓
Check if membership is active
├── NO → ❌ Deny
└── YES ↓
Check if role has required permission
├── NO → ❌ Deny (403 Forbidden)
└── YES → ✅ Allow
```
---
## Using Permissions in Code
### 1. Require Specific Permission
**When to use:** Endpoint needs one specific permission
```python
from fastapi import APIRouter, Depends
from app.api.deps import require_vendor_permission
from app.core.permissions import VendorPermissions
from models.database.user import User
router = APIRouter()
@router.post("/products")
def create_product(
product_data: ProductCreate,
user: User = Depends(
require_vendor_permission(VendorPermissions.PRODUCTS_CREATE.value)
)
):
"""
Create a product.
Required permission: products.create
✅ Owner: Always allowed
✅ Manager: Allowed (has products.create)
✅ Staff: Allowed (has products.create)
❌ Support: Denied (no products.create)
❌ Viewer: Denied (no products.create)
❌ Marketing: Denied (no products.create)
"""
# Create product...
pass
```
---
### 2. Require ANY Permission
**When to use:** Endpoint can be accessed with any of several permissions
```python
@router.get("/dashboard")
def view_dashboard(
user: User = Depends(
require_any_vendor_permission(
VendorPermissions.DASHBOARD_VIEW.value,
VendorPermissions.REPORTS_VIEW.value
)
)
):
"""
View dashboard.
Required: dashboard.view OR reports.view
✅ Owner: Always allowed
✅ Manager: Allowed (has both)
✅ Staff: Allowed (has dashboard.view)
✅ Support: Allowed (has dashboard.view)
✅ Viewer: Allowed (has both)
✅ Marketing: Allowed (has both)
"""
# Show dashboard...
pass
```
---
### 3. Require ALL Permissions
**When to use:** Endpoint needs multiple permissions
```python
@router.post("/products/bulk-delete")
def bulk_delete_products(
user: User = Depends(
require_all_vendor_permissions(
VendorPermissions.PRODUCTS_VIEW.value,
VendorPermissions.PRODUCTS_DELETE.value
)
)
):
"""
Bulk delete products.
Required: products.view AND products.delete
✅ Owner: Always allowed
✅ Manager: Allowed (has both)
❌ Staff: Denied (no products.delete)
❌ Support: Denied (no products.delete)
❌ Viewer: Denied (no products.delete)
❌ Marketing: Denied (no products.delete)
"""
# Delete products...
pass
```
---
### 4. Require Owner Only
**When to use:** Endpoint is owner-only (team management, critical settings)
```python
from app.api.deps import require_vendor_owner
@router.post("/team/invite")
def invite_team_member(
email: str,
role_id: int,
user: User = Depends(require_vendor_owner)
):
"""
Invite a team member.
Required: Must be vendor owner
✅ Owner: Allowed
❌ Manager: Denied (not owner)
❌ All team members: Denied (not owner)
"""
# Invite team member...
pass
```
---
### 5. Get User Permissions
**When to use:** Need to check permissions in business logic
```python
from app.api.deps import get_user_permissions
@router.get("/my-permissions")
def list_my_permissions(
permissions: list = Depends(get_user_permissions)
):
"""
Get all permissions for current user.
Returns:
- Owner: All 75 permissions
- Team Member: Permissions from their role
"""
return {"permissions": permissions}
```
---
## Database Schema
### VendorUser Table
```sql
CREATE TABLE vendor_users (
id SERIAL PRIMARY KEY,
vendor_id INTEGER NOT NULL REFERENCES vendors(id),
user_id INTEGER NOT NULL REFERENCES users(id),
user_type VARCHAR NOT NULL, -- 'owner' or 'member'
role_id INTEGER REFERENCES roles(id), -- NULL for owners
invited_by INTEGER REFERENCES users(id),
invitation_token VARCHAR,
invitation_sent_at TIMESTAMP,
invitation_accepted_at TIMESTAMP,
is_active BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
### Role Table
```sql
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
vendor_id INTEGER NOT NULL REFERENCES vendors(id),
name VARCHAR(100) NOT NULL,
permissions JSON DEFAULT '[]', -- Array of permission strings
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
```
---
## Team Member Lifecycle
### 1. Invitation
```
Owner invites user → VendorUser created:
{
"user_type": "member",
"is_active": False,
"invitation_token": "abc123...",
"invitation_sent_at": "2024-11-29 10:00:00",
"invitation_accepted_at": null
}
```
### 2. Acceptance
```
User accepts invitation → VendorUser updated:
{
"is_active": True,
"invitation_token": null,
"invitation_accepted_at": "2024-11-29 10:30:00"
}
```
### 3. Active Member
```
Member can now access vendor dashboard with role permissions
```
### 4. Deactivation
```
Owner deactivates member → VendorUser updated:
{
"is_active": False
}
```
---
## Common Use Cases
### Use Case 1: Dashboard Access
**Q:** Can all users access the dashboard?
**A:** Yes, if they have `dashboard.view` permission.
- ✅ Owner: Always
- ✅ Manager, Staff, Support, Viewer, Marketing: All have it
- ❌ Custom role without `dashboard.view`: No
---
### Use Case 2: Product Management
**Q:** Who can create products?
**A:** Users with `products.create` permission.
- ✅ Owner: Always
- ✅ Manager: Yes (has permission)
- ✅ Staff: Yes (has permission)
- ❌ Support, Viewer, Marketing: No
---
### Use Case 3: Financial Reports
**Q:** Who can view financial reports?
**A:** Users with `reports.financial` permission.
- ✅ Owner: Always
- ✅ Manager: Yes (has permission)
- ❌ Staff, Support, Viewer, Marketing: No
---
### Use Case 4: Team Management
**Q:** Who can invite team members?
**A:** Only the vendor owner.
- ✅ Owner: Yes (owner-only operation)
- ❌ All team members (including Manager): No
---
### Use Case 5: Settings Changes
**Q:** Who can change vendor settings?
**A:** Users with `settings.edit` permission.
- ✅ Owner: Always
- ❌ Manager: No (doesn't have permission)
- ❌ All other roles: No
---
## Error Responses
### Missing Permission
```http
HTTP 403 Forbidden
{
"error_code": "INSUFFICIENT_VENDOR_PERMISSIONS",
"message": "You don't have permission to perform this action",
"details": {
"required_permission": "products.delete",
"vendor_code": "wizamart"
}
}
```
### Not Owner
```http
HTTP 403 Forbidden
{
"error_code": "VENDOR_OWNER_ONLY",
"message": "This operation requires vendor owner privileges",
"details": {
"operation": "team management",
"vendor_code": "wizamart"
}
}
```
### Inactive Membership
```http
HTTP 403 Forbidden
{
"error_code": "INACTIVE_VENDOR_MEMBERSHIP",
"message": "Your vendor membership is inactive"
}
```
---
## Summary
### Owner vs Team Member
| Feature | Owner | Team Member |
|---------|-------|-------------|
| **Permissions** | All 75 (automatic) | Based on role (0-75) |
| **Role Required** | No | Yes |
| **Can Be Removed** | No | Yes |
| **Team Management** | ✅ Yes | ❌ No |
| **Critical Settings** | ✅ Yes | ❌ No (usually) |
| **Invitation Required** | No (creates vendor) | Yes |
### Permission Hierarchy
```
Owner (75 permissions)
└─ Manager (43 permissions)
└─ Staff (10 permissions)
└─ Support (6 permissions)
└─ Viewer (6 permissions, read-only)
Marketing (7 permissions, specialized)
```
### Best Practices
1. **Use Constants:** Always use `VendorPermissions.PERMISSION_NAME.value`
2. **Least Privilege:** Give team members minimum permissions needed
3. **Owner Only:** Keep sensitive operations owner-only
4. **Custom Roles:** Create custom roles for specific needs
5. **Regular Audit:** Review team member permissions regularly
---
This RBAC system provides flexible, secure access control for vendor dashboards with clear separation between owners and team members.