refactor: rename public routes and templates to platform

Complete the public -> platform naming migration across the codebase.
This aligns with the naming convention where "platform" refers to
the marketing/public-facing pages of the platform itself.

Changes:
- Update all imports from public to platform modules
- Update template references from public/ to platform/
- Update route registrations to use platform prefix
- Update documentation to reflect new naming
- Update test files for platform API endpoints

Files affected:
- app/api/main.py - router imports
- app/modules/*/routes/*/platform.py - route definitions
- app/modules/*/templates/*/platform/ - template files
- app/modules/routes.py - route discovery
- docs/* - documentation updates
- tests/integration/api/v1/platform/ - test files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-02 18:49:39 +01:00
parent 967f08e4ba
commit fb8cb14506
44 changed files with 980 additions and 327 deletions

View File

@@ -93,7 +93,7 @@ POST /api/v1/vendor/auth/login
Body: {"username": "...", "password": "..."}
# Customer
POST /api/v1/public/vendors/{vendor_id}/customers/login
POST /api/v1/platform/vendors/{vendor_id}/customers/login
Body: {"username": "...", "password": "..."}
```

View File

@@ -229,12 +229,12 @@ In path-based development mode, the full URL includes the vendor code (e.g., `/v
**Login Endpoint:**
```
POST /api/v1/public/vendors/{vendor_id}/customers/login
POST /api/v1/platform/vendors/{vendor_id}/customers/login
```
**Example Request:**
```bash
curl -X POST http://localhost:8000/api/v1/public/vendors/1/customers/login \
curl -X POST http://localhost:8000/api/v1/platform/vendors/1/customers/login \
-H "Content-Type: application/json" \
-d '{"username":"customer","password":"customer123"}'
```
@@ -950,7 +950,7 @@ curl http://localhost:8000/api/v1/admin/vendors \
```bash
# Login
curl -X POST http://localhost:8000/api/v1/public/vendors/1/customers/login \
curl -X POST http://localhost:8000/api/v1/platform/vendors/1/customers/login \
-H "Content-Type: application/json" \
-d '{"username":"customer","password":"customer123"}'

View File

@@ -748,7 +748,7 @@ role = Role(
│ Client │
└──────┬──────┘
│ POST /api/v1/public/vendors/{id}/customers/login
│ POST /api/v1/platform/vendors/{id}/customers/login
│ { username, password }
┌─────────────────────────────┐

View File

@@ -814,8 +814,8 @@ X-RateLimit-Reset: 1700000000
**Old Pattern (Deprecated):**
```http
GET /api/v1/public/vendors/{vendor_id}/products
POST /api/v1/public/vendors/auth/{vendor_id}/customers/login
GET /api/v1/platform/vendors/{vendor_id}/products
POST /api/v1/platform/vendors/auth/{vendor_id}/customers/login
```
**New Pattern (Current):**

View File

@@ -7,7 +7,7 @@
## Executive Summary
The platform currently has **two parallel API structures** for shop/customer-facing endpoints:
1. **Original:** `/api/v1/public/vendors/{vendor_id}/*`
1. **Original:** `/api/v1/platform/vendors/{vendor_id}/*`
2. **New:** `/api/v1/shop/*`
This divergence creates confusion, maintenance overhead, and potential bugs. This document analyzes the situation and proposes a consolidation strategy.
@@ -16,19 +16,19 @@ This divergence creates confusion, maintenance overhead, and potential bugs. Thi
## Current State Analysis
### 1. Original Architecture (`/api/v1/public/vendors/`)
### 1. Original Architecture (`/api/v1/platform/vendors/`)
**Location:** `app/api/v1/public/vendors/`
**Location:** `app/api/v1/platform/vendors/`
**Endpoints:**
```
GET /api/v1/public/vendors → List active vendors
GET /api/v1/public/vendors/{vendor_id}/products → Product catalog
GET /api/v1/public/vendors/{vendor_id}/products/{product_id} → Product detail
POST /api/v1/public/vendors/{vendor_id}/cart → Cart operations
GET /api/v1/public/vendors/{vendor_id}/orders → Customer orders
POST /api/v1/public/vendors/auth/login → Customer authentication
POST /api/v1/public/vendors/auth/register → Customer registration
GET /api/v1/platform/vendors → List active vendors
GET /api/v1/platform/vendors/{vendor_id}/products → Product catalog
GET /api/v1/platform/vendors/{vendor_id}/products/{product_id} → Product detail
POST /api/v1/platform/vendors/{vendor_id}/cart → Cart operations
GET /api/v1/platform/vendors/{vendor_id}/orders → Customer orders
POST /api/v1/platform/vendors/auth/login → Customer authentication
POST /api/v1/platform/vendors/auth/register → Customer registration
```
**Characteristics:**
@@ -60,7 +60,7 @@ GET /api/v1/shop/content-pages/{slug} → CMS page content
**Characteristics:**
-**Vendor-agnostic URLs:** Clean paths without vendor_id
-**Middleware-driven:** Relies on `VendorContextMiddleware` to inject vendor
-**Simpler URLs:** `/api/v1/shop/products` vs `/api/v1/public/vendors/123/products`
-**Simpler URLs:** `/api/v1/shop/products` vs `/api/v1/platform/vendors/123/products`
-**Incomplete:** Only CMS endpoints implemented
-**Divergent:** Not consistent with existing public API
@@ -80,7 +80,7 @@ GET /api/v1/shop/content-pages/{slug} → CMS page content
fetch('/api/v1/shop/content-pages/about')
// Products use old pattern
fetch('/api/v1/public/vendors/123/products')
fetch('/api/v1/platform/vendors/123/products')
```
### Confusion
@@ -143,7 +143,7 @@ Developers must remember:
**Implementation:**
- Vendor extracted by `VendorContextMiddleware` from request
- All endpoints use `request.state.vendor` instead of path parameter
- URLs are cleaner: `/api/v1/shop/products` instead of `/api/v1/public/vendors/123/products`
- URLs are cleaner: `/api/v1/shop/products` instead of `/api/v1/platform/vendors/123/products`
**Pros:**
- ✅ Clean, consistent API structure
@@ -162,18 +162,18 @@ Developers must remember:
---
### Option 2: Keep `/api/v1/public/vendors/*` and Deprecate `/api/v1/shop/*`
### Option 2: Keep `/api/v1/platform/vendors/*` and Deprecate `/api/v1/shop/*`
**Approach:** Move CMS endpoints to `/api/v1/public/vendors/{vendor_id}/content-pages/*`
**Approach:** Move CMS endpoints to `/api/v1/platform/vendors/{vendor_id}/content-pages/*`
**Proposed Changes:**
```
# Move CMS endpoints
FROM: /api/v1/shop/content-pages/navigation
TO: /api/v1/public/vendors/{vendor_id}/content-pages/navigation
TO: /api/v1/platform/vendors/{vendor_id}/content-pages/navigation
FROM: /api/v1/shop/content-pages/{slug}
TO: /api/v1/public/vendors/{vendor_id}/content-pages/{slug}
TO: /api/v1/platform/vendors/{vendor_id}/content-pages/{slug}
```
**Pros:**
@@ -240,7 +240,7 @@ async def get_products_legacy(vendor_id: int, db: Session = Depends(get_db)):
fetch('/api/v1/shop/products')
// ❌ BAD
fetch('/api/v1/public/vendors/123/products')
fetch('/api/v1/platform/vendors/123/products')
```
3. **Multi-Tenant Best Practice**: Vendor context should be implicit (from domain/path), not explicit in every API call.
@@ -258,7 +258,7 @@ async def get_products_legacy(vendor_id: int, db: Session = Depends(get_db)):
**Day 1-2: Move Products**
```bash
# Copy and adapt
app/api/v1/public/vendors/products.py → app/api/v1/shop/products.py
app/api/v1/platform/vendors/products.py → app/api/v1/shop/products.py
# Changes:
- Remove vendor_id path parameter
@@ -268,24 +268,24 @@ app/api/v1/public/vendors/products.py → app/api/v1/shop/products.py
**Day 3: Move Cart**
```bash
app/api/v1/public/vendors/cart.py → app/api/v1/shop/cart.py
app/api/v1/platform/vendors/cart.py → app/api/v1/shop/cart.py
```
**Day 4: Move Orders**
```bash
app/api/v1/public/vendors/orders.py → app/api/v1/shop/orders.py
app/api/v1/platform/vendors/orders.py → app/api/v1/shop/orders.py
```
**Day 5: Move Auth**
```bash
app/api/v1/public/vendors/auth.py → app/api/v1/shop/auth.py
app/api/v1/platform/vendors/auth.py → app/api/v1/shop/auth.py
```
### Phase 2: Update Frontend (Week 1)
**Templates:**
- Update all `fetch()` calls in shop templates
- Change from `/api/v1/public/vendors/${vendorId}/...` to `/api/v1/shop/...`
- Change from `/api/v1/platform/vendors/${vendorId}/...` to `/api/v1/shop/...`
**JavaScript:**
- Update any shop-related API client code
@@ -316,10 +316,10 @@ app/api/v1/public/vendors/auth.py → app/api/v1/shop/auth.py
## Code Examples
### Before (Current - `/api/v1/public/vendors`)
### Before (Current - `/api/v1/platform/vendors`)
```python
# app/api/v1/public/vendors/products.py
# app/api/v1/platform/vendors/products.py
@router.get("/{vendor_id}/products")
def get_public_product_catalog(
vendor_id: int = Path(...),
@@ -332,7 +332,7 @@ def get_public_product_catalog(
```javascript
// Frontend
const vendorId = 123;
fetch(`/api/v1/public/vendors/${vendorId}/products`)
fetch(`/api/v1/platform/vendors/${vendorId}/products`)
```
### After (Proposed - `/api/v1/shop`)
@@ -358,14 +358,14 @@ fetch('/api/v1/shop/products') // Vendor context automatic
## Impact Assessment
### Breaking Changes
- All frontend code calling `/api/v1/public/vendors/*` must update
- All frontend code calling `/api/v1/platform/vendors/*` must update
- Mobile apps (if any) must update
- Third-party integrations (if any) must update
### Non-Breaking
- Admin APIs: `/api/v1/admin/*` → No changes
- Vendor APIs: `/api/v1/vendor/*` → No changes
- Vendor listing: Keep `/api/v1/public/vendors` (list all vendors for marketplace)
- Vendor listing: Keep `/api/v1/platform/vendors` (list all vendors for marketplace)
### Risk Mitigation
1. **Deprecation Period**: Keep old endpoints for 2-4 weeks
@@ -383,7 +383,7 @@ If full migration is not approved immediately, we can do a **minimal fix** for t
```python
# Move: app/api/v1/shop/content_pages.py
# To: app/api/v1/public/vendors/content_pages.py
# To: app/api/v1/platform/vendors/content_pages.py
# Update routes:
@router.get("/{vendor_id}/content-pages/navigation")
@@ -402,7 +402,7 @@ If full migration is not approved immediately, we can do a **minimal fix** for t
Should we:
1. ✅ **Consolidate to `/api/v1/shop/*`** (Recommended)
2. ❌ **Keep `/api/v1/public/vendors/*`** and move CMS there
2. ❌ **Keep `/api/v1/platform/vendors/*`** and move CMS there
3. ❌ **Hybrid approach** with both patterns
4. ❌ **Quick fix only** - move CMS, address later
@@ -412,7 +412,7 @@ Should we:
## Appendix: Current Endpoint Inventory
### `/api/v1/public/vendors/*`
### `/api/v1/platform/vendors/*`
- ✅ `vendors.py` - Vendor listing
- ✅ `auth.py` - Customer authentication
- ✅ `products.py` - Product catalog

View File

@@ -174,15 +174,15 @@ Updated all shop templates to use new API endpoints:
| Template | Old Endpoint | New Endpoint | Status |
|----------|-------------|--------------|---------|
| `shop/account/login.html` | `/api/v1/public/vendors/${id}/customers/login` | `/api/v1/shop/auth/login` | ✅ Complete |
| `shop/account/register.html` | `/api/v1/public/vendors/${id}/customers/register` | `/api/v1/shop/auth/register` | ✅ Complete |
| `shop/product.html` | `/api/v1/public/vendors/${id}/products/${pid}` | `/api/v1/shop/products/${pid}` | ✅ Complete |
| `shop/product.html` | `/api/v1/public/vendors/${id}/products?limit=4` | `/api/v1/shop/products?limit=4` | ✅ Complete |
| `shop/product.html` | `/api/v1/public/vendors/${id}/cart/${sid}` | `/api/v1/shop/cart/${sid}` | ✅ Complete |
| `shop/product.html` | `/api/v1/public/vendors/${id}/cart/${sid}/items` | `/api/v1/shop/cart/${sid}/items` | ✅ Complete |
| `shop/cart.html` | `/api/v1/public/vendors/${id}/cart/${sid}` | `/api/v1/shop/cart/${sid}` | ✅ Complete |
| `shop/cart.html` | `/api/v1/public/vendors/${id}/cart/${sid}/items/${pid}` (PUT) | `/api/v1/shop/cart/${sid}/items/${pid}` | ✅ Complete |
| `shop/cart.html` | `/api/v1/public/vendors/${id}/cart/${sid}/items/${pid}` (DELETE) | `/api/v1/shop/cart/${sid}/items/${pid}` | ✅ Complete |
| `shop/account/login.html` | `/api/v1/platform/vendors/${id}/customers/login` | `/api/v1/shop/auth/login` | ✅ Complete |
| `shop/account/register.html` | `/api/v1/platform/vendors/${id}/customers/register` | `/api/v1/shop/auth/register` | ✅ Complete |
| `shop/product.html` | `/api/v1/platform/vendors/${id}/products/${pid}` | `/api/v1/shop/products/${pid}` | ✅ Complete |
| `shop/product.html` | `/api/v1/platform/vendors/${id}/products?limit=4` | `/api/v1/shop/products?limit=4` | ✅ Complete |
| `shop/product.html` | `/api/v1/platform/vendors/${id}/cart/${sid}` | `/api/v1/shop/cart/${sid}` | ✅ Complete |
| `shop/product.html` | `/api/v1/platform/vendors/${id}/cart/${sid}/items` | `/api/v1/shop/cart/${sid}/items` | ✅ Complete |
| `shop/cart.html` | `/api/v1/platform/vendors/${id}/cart/${sid}` | `/api/v1/shop/cart/${sid}` | ✅ Complete |
| `shop/cart.html` | `/api/v1/platform/vendors/${id}/cart/${sid}/items/${pid}` (PUT) | `/api/v1/shop/cart/${sid}/items/${pid}` | ✅ Complete |
| `shop/cart.html` | `/api/v1/platform/vendors/${id}/cart/${sid}/items/${pid}` (DELETE) | `/api/v1/shop/cart/${sid}/items/${pid}` | ✅ Complete |
| `shop/products.html` | Already using `/api/v1/shop/products` | (No change needed) | ✅ Already Updated |
| `shop/home.html` | Already using `/api/v1/shop/products?featured=true` | (No change needed) | ✅ Already Updated |
@@ -196,7 +196,7 @@ grep -r "api/v1/public/vendors" app/templates/shop --include="*.html"
### ✅ Phase 3: Old Endpoint Cleanup (COMPLETE)
Cleaned up old `/api/v1/public/vendors/*` endpoints:
Cleaned up old `/api/v1/platform/vendors/*` endpoints:
**Files Removed:**
-`auth.py` - Migrated to `/api/v1/shop/auth.py`
@@ -209,14 +209,14 @@ Cleaned up old `/api/v1/public/vendors/*` endpoints:
**Files Kept:**
-`vendors.py` - Vendor lookup endpoints (truly public, not shop-specific)
- `GET /api/v1/public/vendors/by-code/{vendor_code}`
- `GET /api/v1/public/vendors/by-subdomain/{subdomain}`
- `GET /api/v1/public/vendors/{vendor_id}/info`
- `GET /api/v1/platform/vendors/by-code/{vendor_code}`
- `GET /api/v1/platform/vendors/by-subdomain/{subdomain}`
- `GET /api/v1/platform/vendors/{vendor_id}/info`
**Updated:**
-`/app/api/v1/public/__init__.py` - Now only includes vendor lookup endpoints
-`/app/api/v1/platform/__init__.py` - Now only includes vendor lookup endpoints
**Result:** Old shop endpoints completely removed, only vendor lookup remains in `/api/v1/public/vendors/*`
**Result:** Old shop endpoints completely removed, only vendor lookup remains in `/api/v1/platform/vendors/*`
### ⚠️ Phase 4: Deprecation Warnings (SKIPPED - Not Needed)
@@ -252,16 +252,16 @@ Old endpoint cleanup completed immediately (no gradual migration needed):
1. ✅ Removed old endpoint files:
```bash
rm app/api/v1/public/vendors/products.py
rm app/api/v1/public/vendors/cart.py
rm app/api/v1/public/vendors/orders.py
rm app/api/v1/public/vendors/auth.py
rm app/api/v1/public/vendors/payments.py
rm app/api/v1/public/vendors/search.py
rm app/api/v1/public/vendors/shop.py
rm app/api/v1/platform/vendors/products.py
rm app/api/v1/platform/vendors/cart.py
rm app/api/v1/platform/vendors/orders.py
rm app/api/v1/platform/vendors/auth.py
rm app/api/v1/platform/vendors/payments.py
rm app/api/v1/platform/vendors/search.py
rm app/api/v1/platform/vendors/shop.py
```
2. ✅ Updated `/api/v1/public/__init__.py`:
2. ✅ Updated `/api/v1/platform/__init__.py`:
```python
# Only import vendor lookup endpoints
from .vendors import vendors
@@ -280,12 +280,12 @@ Old endpoint cleanup completed immediately (no gradual migration needed):
### Before (Old Pattern)
```
# Verbose - requires vendor_id everywhere
/api/v1/public/vendors/123/products
/api/v1/public/vendors/123/products/456
/api/v1/public/vendors/123/cart/abc-session-id
/api/v1/public/vendors/123/cart/abc-session-id/items
/api/v1/public/vendors/123/customers/789/orders
/api/v1/public/vendors/auth/123/customers/login
/api/v1/platform/vendors/123/products
/api/v1/platform/vendors/123/products/456
/api/v1/platform/vendors/123/cart/abc-session-id
/api/v1/platform/vendors/123/cart/abc-session-id/items
/api/v1/platform/vendors/123/customers/789/orders
/api/v1/platform/vendors/auth/123/customers/login
```
### After (New Pattern)

View File

@@ -452,7 +452,7 @@ All frontends communicate with backend via APIs:
- `/api/v1/admin/*` - Admin APIs
- `/api/v1/vendor/*` - Vendor APIs
- `/api/v1/storefront/*` - Storefront APIs
- `/api/v1/public/*` - Platform APIs
- `/api/v1/platform/*` - Platform APIs
**Benefits:**
- Clear backend contracts

View File

@@ -472,7 +472,7 @@ async def setup_payments(
}
# app/api/v1/public/vendors/payments.py
# app/api/v1/platform/vendors/payments.py
@router.post("/{vendor_id}/payments/create-intent")
async def create_payment_intent(
vendor_id: int,
@@ -535,7 +535,7 @@ class CheckoutManager {
async initializePayment(orderData) {
// Create payment intent
const response = await fetch(`/api/v1/public/vendors/${this.vendorId}/payments/create-intent`, {
const response = await fetch(`/api/v1/platform/vendors/${this.vendorId}/payments/create-intent`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -585,7 +585,7 @@ Customer proceeds to checkout
System creates Order (payment_status: pending)
Frontend calls POST /api/v1/public/vendors/{vendor_id}/payments/create-intent
Frontend calls POST /api/v1/platform/vendors/{vendor_id}/payments/create-intent
PaymentService creates Stripe PaymentIntent with vendor destination

View File

@@ -53,7 +53,7 @@ app/api/v1/vendor/
├── inventory.py # Handles inventory items
└── settings.py # Exception: not a resource collection
app/api/v1/public/vendors/
app/api/v1/platform/vendors/
├── products.py # Public product catalog
├── orders.py # Order placement
└── auth.py # Exception: authentication service

View File

@@ -132,24 +132,24 @@ Standalone page with:
## API Endpoints
All endpoints under `/api/v1/public/`:
All endpoints under `/api/v1/platform/`:
### Pricing Endpoints
```
GET /api/v1/public/tiers
GET /api/v1/platform/tiers
Returns all public subscription tiers
Response: TierResponse[]
GET /api/v1/public/tiers/{tier_code}
GET /api/v1/platform/tiers/{tier_code}
Returns specific tier by code
Response: TierResponse
GET /api/v1/public/addons
GET /api/v1/platform/addons
Returns all active add-on products
Response: AddOnResponse[]
GET /api/v1/public/pricing
GET /api/v1/platform/pricing
Returns complete pricing info (tiers + addons + trial_days)
Response: PricingResponse
```
@@ -157,17 +157,17 @@ GET /api/v1/public/pricing
### Letzshop Vendor Endpoints
```
GET /api/v1/public/letzshop-vendors
GET /api/v1/platform/letzshop-vendors
Query params: ?search=&category=&city=&page=1&limit=20
Returns paginated vendor list (placeholder for future)
Response: LetzshopVendorListResponse
POST /api/v1/public/letzshop-vendors/lookup
POST /api/v1/platform/letzshop-vendors/lookup
Body: { "url": "letzshop.lu/vendors/my-shop" }
Returns vendor info from URL lookup
Response: LetzshopLookupResponse
GET /api/v1/public/letzshop-vendors/{slug}
GET /api/v1/platform/letzshop-vendors/{slug}
Returns vendor info by slug
Response: LetzshopVendorInfo
```
@@ -175,17 +175,17 @@ GET /api/v1/public/letzshop-vendors/{slug}
### Signup Endpoints
```
POST /api/v1/public/signup/start
POST /api/v1/platform/signup/start
Body: { "tier_code": "professional", "is_annual": false }
Creates signup session
Response: { "session_id": "...", "tier_code": "...", "is_annual": false }
POST /api/v1/public/signup/claim-vendor
POST /api/v1/platform/signup/claim-vendor
Body: { "session_id": "...", "letzshop_slug": "my-shop" }
Claims Letzshop vendor for session
Response: { "session_id": "...", "letzshop_slug": "...", "vendor_name": "..." }
POST /api/v1/public/signup/create-account
POST /api/v1/platform/signup/create-account
Body: {
"session_id": "...",
"email": "user@example.com",
@@ -197,17 +197,17 @@ POST /api/v1/public/signup/create-account
Creates User, Company, Vendor, Stripe Customer
Response: { "session_id": "...", "user_id": 1, "vendor_id": 1, "stripe_customer_id": "cus_..." }
POST /api/v1/public/signup/setup-payment
POST /api/v1/platform/signup/setup-payment
Body: { "session_id": "..." }
Creates Stripe SetupIntent
Response: { "session_id": "...", "client_secret": "seti_...", "stripe_customer_id": "cus_..." }
POST /api/v1/public/signup/complete
POST /api/v1/platform/signup/complete
Body: { "session_id": "...", "setup_intent_id": "seti_..." }
Completes signup, attaches payment method
Response: { "success": true, "vendor_code": "...", "vendor_id": 1, "redirect_url": "...", "trial_ends_at": "..." }
GET /api/v1/public/signup/session/{session_id}
GET /api/v1/platform/signup/session/{session_id}
Returns session status for resuming signup
Response: { "session_id": "...", "step": "...", ... }
```
@@ -430,7 +430,7 @@ STRIPE_TRIAL_DAYS=30
### Automated Tests
Test files located in `tests/integration/api/v1/public/`:
Test files located in `tests/integration/api/v1/platform/`:
| File | Tests | Description |
|------|-------|-------------|
@@ -440,7 +440,7 @@ Test files located in `tests/integration/api/v1/public/`:
**Run tests:**
```bash
pytest tests/integration/api/v1/public/ -v
pytest tests/integration/api/v1/platform/ -v
```
**Test categories:**
@@ -479,15 +479,15 @@ pytest tests/integration/api/v1/public/ -v
```bash
# Get pricing
curl http://localhost:8000/api/v1/public/pricing
curl http://localhost:8000/api/v1/platform/pricing
# Lookup vendor
curl -X POST http://localhost:8000/api/v1/public/letzshop-vendors/lookup \
curl -X POST http://localhost:8000/api/v1/platform/letzshop-vendors/lookup \
-H "Content-Type: application/json" \
-d '{"url": "letzshop.lu/vendors/test-shop"}'
# Start signup
curl -X POST http://localhost:8000/api/v1/public/signup/start \
curl -X POST http://localhost:8000/api/v1/platform/signup/start \
-H "Content-Type: application/json" \
-d '{"tier_code": "professional", "is_annual": false}'
```

View File

@@ -0,0 +1,653 @@
# Plan: Flexible Role & Permission Management with Platform Controls
## Status: READY FOR APPROVAL
## Summary
Design a flexible role/permission management system that:
1. **Modules define permissions** - Each module declares its available permissions
2. **Platforms control availability** - Platforms can restrict which permissions vendors can use
3. **Vendors customize roles** - Vendors create custom roles within platform constraints
4. **Multi-tier hierarchy** - Platform → Vendor → User permission inheritance
---
## Current State Analysis
### What Exists Today
| Component | Location | Description |
|-----------|----------|-------------|
| **Role Model** | `app/modules/tenancy/models/vendor.py` | `vendor_id`, `name`, `permissions` (JSON array) |
| **VendorUser Model** | Same file | Links user → vendor with `role_id` |
| **PermissionDiscoveryService** | `app/modules/tenancy/services/permission_discovery_service.py` | Discovers permissions from modules |
| **VendorTeamService** | `app/modules/tenancy/services/vendor_team_service.py` | Manages team invitations, role assignment |
| **Role Presets** | In discovery service code | Hardcoded `ROLE_PRESETS` dict |
| **Platform Model** | `models/database/platform.py` | Multi-platform support |
| **PlatformModule** | `models/database/platform_module.py` | Controls which modules are enabled per platform |
| **VendorPlatform** | `models/database/vendor_platform.py` | Vendor-platform relationship with `tier_id` |
### Current Gaps
1. **No platform-level permission control** - Platforms cannot restrict which permissions vendors can assign
2. **No custom role CRUD API** - Roles are created implicitly when inviting team members
3. **Presets are code-only** - Cannot customize role templates per platform
4. **No role templates table** - Platform admins cannot define default roles for their vendors
---
## Proposed Architecture
### Tier 1: Module-Defined Permissions (Exists)
Each module declares permissions in `definition.py`:
```python
permissions=[
PermissionDefinition(
id="products.view",
label_key="catalog.permissions.products_view",
category="products",
),
PermissionDefinition(
id="products.create",
label_key="catalog.permissions.products_create",
category="products",
),
]
```
**Discovery Service** aggregates all permissions at runtime.
### Tier 2: Platform Permission Control (New)
New `PlatformPermissionConfig` model to control:
- Which permissions are available to vendors on this platform
- Default role templates for vendor onboarding
- Permission bundles based on subscription tier
```
┌─────────────────────────────────────────────────────────────────┐
│ PLATFORM │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PlatformPermissionConfig │ │
│ │ - platform_id │ │
│ │ - allowed_permissions: ["products.*", "orders.*"] │ │
│ │ - blocked_permissions: ["team.manage"] │ │
│ │ - tier_restrictions: {free: [...], pro: [...]} │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PlatformRoleTemplate │ │
│ │ - platform_id │ │
│ │ - name: "Manager", "Staff", etc. │ │
│ │ - permissions: [...] │ │
│ │ - is_default: bool (create for new vendors) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### Tier 3: Vendor Role Customization (Enhanced)
Vendors can:
- View roles available (from platform templates or custom)
- Create custom roles (within platform constraints)
- Edit role permissions (within allowed set)
- Assign roles to team members
```
┌─────────────────────────────────────────────────────────────────┐
│ VENDOR │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Role (existing model, enhanced) │ │
│ │ - vendor_id │ │
│ │ - name │ │
│ │ - permissions: [...] (validated against platform) │ │
│ │ - is_from_template: bool │ │
│ │ - source_template_id: FK (nullable) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ VendorUser (existing, unchanged) │ │
│ │ - user_id │ │
│ │ - vendor_id │ │
│ │ - role_id │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
---
## Data Model Changes
### New Models
#### 1. PlatformPermissionConfig
```python
# app/modules/tenancy/models/platform_permission_config.py
class PlatformPermissionConfig(Base):
"""Platform-level permission configuration"""
__tablename__ = "platform_permission_configs"
id: Mapped[int] = mapped_column(primary_key=True)
platform_id: Mapped[int] = mapped_column(ForeignKey("platforms.id"), unique=True)
# Permissions this platform allows vendors to use
# Empty = all discovered permissions allowed
allowed_permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
# Explicit blocklist (takes precedence over allowed)
blocked_permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
# Tier-based restrictions: {"free": ["products.view"], "pro": ["products.*"]}
tier_permissions: Mapped[dict] = mapped_column(JSON, default=dict)
created_at: Mapped[datetime] = mapped_column(default=func.now())
updated_at: Mapped[datetime] = mapped_column(onupdate=func.now())
# Relationships
platform: Mapped["Platform"] = relationship(back_populates="permission_config")
```
#### 2. PlatformRoleTemplate
```python
# app/modules/tenancy/models/platform_role_template.py
class PlatformRoleTemplate(Base):
"""Role templates defined at platform level"""
__tablename__ = "platform_role_templates"
id: Mapped[int] = mapped_column(primary_key=True)
platform_id: Mapped[int] = mapped_column(ForeignKey("platforms.id"))
name: Mapped[str] = mapped_column(String(50)) # "Manager", "Staff", etc.
display_name: Mapped[str] = mapped_column(String(100)) # i18n key or display name
description: Mapped[str | None] = mapped_column(String(255))
# Permissions for this template
permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
# Configuration
is_default: Mapped[bool] = mapped_column(default=False) # Auto-create for new vendors
is_system: Mapped[bool] = mapped_column(default=False) # Cannot be deleted
order: Mapped[int] = mapped_column(default=100) # Display order
created_at: Mapped[datetime] = mapped_column(default=func.now())
updated_at: Mapped[datetime] = mapped_column(onupdate=func.now())
# Relationships
platform: Mapped["Platform"] = relationship(back_populates="role_templates")
__table_args__ = (
UniqueConstraint("platform_id", "name", name="uq_platform_role_template"),
)
```
### Enhanced Existing Models
#### Role Model Enhancement
```python
# Add to existing Role model in app/modules/tenancy/models/vendor.py
class Role(Base):
# ... existing fields ...
# NEW: Track template origin
source_template_id: Mapped[int | None] = mapped_column(
ForeignKey("platform_role_templates.id"),
nullable=True
)
is_custom: Mapped[bool] = mapped_column(default=False) # Vendor-created custom role
# Relationship
source_template: Mapped["PlatformRoleTemplate"] = relationship()
```
---
## Service Layer Changes
### 1. PlatformPermissionService (New)
```python
# app/modules/tenancy/services/platform_permission_service.py
class PlatformPermissionService:
"""Manages platform-level permission configuration"""
def get_allowed_permissions(
self,
db: Session,
platform_id: int,
tier_id: int | None = None
) -> set[str]:
"""
Get permissions allowed for a platform/tier combination.
1. Start with all discovered permissions
2. Filter by platform's allowed_permissions (if set)
3. Remove blocked_permissions
4. Apply tier restrictions (if tier_id provided)
"""
pass
def validate_permissions(
self,
db: Session,
platform_id: int,
tier_id: int | None,
permissions: list[str]
) -> tuple[list[str], list[str]]:
"""
Validate permissions against platform constraints.
Returns (valid_permissions, invalid_permissions)
"""
pass
def update_platform_config(
self,
db: Session,
platform_id: int,
allowed_permissions: list[str] | None = None,
blocked_permissions: list[str] | None = None,
tier_permissions: dict | None = None
) -> PlatformPermissionConfig:
"""Update platform permission configuration"""
pass
```
### 2. PlatformRoleTemplateService (New)
```python
# app/modules/tenancy/services/platform_role_template_service.py
class PlatformRoleTemplateService:
"""Manages platform role templates"""
def get_templates(self, db: Session, platform_id: int) -> list[PlatformRoleTemplate]:
"""Get all role templates for a platform"""
pass
def create_template(
self,
db: Session,
platform_id: int,
name: str,
permissions: list[str],
is_default: bool = False
) -> PlatformRoleTemplate:
"""Create a new role template (validates permissions)"""
pass
def create_default_roles_for_vendor(
self,
db: Session,
vendor: Vendor
) -> list[Role]:
"""
Create vendor roles from platform's default templates.
Called during vendor onboarding.
"""
pass
def seed_default_templates(self, db: Session, platform_id: int):
"""Seed platform with standard role templates (Manager, Staff, etc.)"""
pass
```
### 3. Enhanced VendorTeamService
```python
# Updates to app/modules/tenancy/services/vendor_team_service.py
class VendorTeamService:
def get_available_permissions(
self,
db: Session,
vendor: Vendor
) -> list[PermissionDefinition]:
"""
Get permissions available to this vendor based on:
1. Platform constraints
2. Vendor's subscription tier
"""
platform_perm_service = PlatformPermissionService()
vendor_platform = db.query(VendorPlatform).filter(...).first()
allowed = platform_perm_service.get_allowed_permissions(
db,
vendor_platform.platform_id,
vendor_platform.tier_id
)
# Return PermissionDefinitions filtered to allowed set
all_perms = permission_discovery_service.get_all_permissions()
return [p for p in all_perms if p.id in allowed]
def create_custom_role(
self,
db: Session,
vendor: Vendor,
name: str,
permissions: list[str]
) -> Role:
"""
Create a custom role for the vendor.
Validates permissions against platform constraints.
"""
# Validate permissions
valid, invalid = self.platform_permission_service.validate_permissions(
db, vendor.platform_id, vendor.tier_id, permissions
)
if invalid:
raise InvalidPermissionsException(invalid)
role = Role(
vendor_id=vendor.id,
name=name,
permissions=valid,
is_custom=True
)
db.add(role)
return role
def update_role(
self,
db: Session,
vendor: Vendor,
role_id: int,
name: str | None = None,
permissions: list[str] | None = None
) -> Role:
"""Update an existing role (validates permissions)"""
pass
def delete_role(
self,
db: Session,
vendor: Vendor,
role_id: int
) -> bool:
"""Delete a custom role (cannot delete if in use)"""
pass
```
---
## API Endpoints
### Platform Admin Endpoints (Admin Panel)
```python
# app/modules/tenancy/routes/admin/platform_permissions.py
@router.get("/platforms/{platform_id}/permissions")
def get_platform_permission_config(platform_id: int):
"""Get platform permission configuration"""
@router.put("/platforms/{platform_id}/permissions")
def update_platform_permission_config(platform_id: int, config: PermissionConfigUpdate):
"""Update platform permission configuration"""
@router.get("/platforms/{platform_id}/role-templates")
def list_role_templates(platform_id: int):
"""List role templates for a platform"""
@router.post("/platforms/{platform_id}/role-templates")
def create_role_template(platform_id: int, template: RoleTemplateCreate):
"""Create a new role template"""
@router.put("/platforms/{platform_id}/role-templates/{template_id}")
def update_role_template(platform_id: int, template_id: int, template: RoleTemplateUpdate):
"""Update a role template"""
@router.delete("/platforms/{platform_id}/role-templates/{template_id}")
def delete_role_template(platform_id: int, template_id: int):
"""Delete a role template"""
```
### Vendor Dashboard Endpoints
```python
# app/modules/tenancy/routes/api/vendor_roles.py
@router.get("/roles")
def list_vendor_roles():
"""List all roles for current vendor"""
@router.post("/roles")
def create_custom_role(role: RoleCreate):
"""Create a custom role (validates permissions against platform)"""
@router.put("/roles/{role_id}")
def update_role(role_id: int, role: RoleUpdate):
"""Update a role"""
@router.delete("/roles/{role_id}")
def delete_role(role_id: int):
"""Delete a custom role"""
@router.get("/available-permissions")
def get_available_permissions():
"""Get permissions available to this vendor (filtered by platform/tier)"""
```
---
## Permission Flow Diagram
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ PERMISSION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
1. MODULE DEFINES PERMISSIONS
┌──────────────┐
│ catalog │ → products.view, products.create, products.edit, ...
│ orders │ → orders.view, orders.manage, orders.refund, ...
│ team │ → team.view, team.manage, team.invite, ...
└──────────────┘
2. DISCOVERY SERVICE AGGREGATES
┌────────────────────────────────────────────────┐
│ PermissionDiscoveryService │
│ get_all_permissions() → 50+ permissions │
└────────────────────────────────────────────────┘
3. PLATFORM FILTERS PERMISSIONS
┌────────────────────────────────────────────────┐
│ PlatformPermissionConfig │
│ allowed: ["products.*", "orders.view"] │
│ blocked: ["orders.refund"] │
│ tier_permissions: │
│ free: ["products.view", "orders.view"] │
│ pro: ["products.*", "orders.*"] │
└────────────────────────────────────────────────┘
4. VENDOR CREATES/USES ROLES
┌────────────────────────────────────────────────┐
│ Role (vendor-specific) │
│ Manager: [products.*, orders.view] │
│ Staff: [products.view, orders.view] │
└────────────────────────────────────────────────┘
5. USER GETS PERMISSIONS VIA ROLE
┌────────────────────────────────────────────────┐
│ VendorUser │
│ user_id: 123 │
│ role_id: 5 (Staff) │
│ → permissions: [products.view, orders.view] │
└────────────────────────────────────────────────┘
```
---
## Database Migrations
### Migration 1: Add Platform Permission Config
```sql
CREATE TABLE platform_permission_configs (
id SERIAL PRIMARY KEY,
platform_id INTEGER NOT NULL UNIQUE REFERENCES platforms(id),
allowed_permissions JSONB DEFAULT '[]',
blocked_permissions JSONB DEFAULT '[]',
tier_permissions JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP
);
```
### Migration 2: Add Platform Role Templates
```sql
CREATE TABLE platform_role_templates (
id SERIAL PRIMARY KEY,
platform_id INTEGER NOT NULL REFERENCES platforms(id),
name VARCHAR(50) NOT NULL,
display_name VARCHAR(100) NOT NULL,
description VARCHAR(255),
permissions JSONB DEFAULT '[]',
is_default BOOLEAN DEFAULT FALSE,
is_system BOOLEAN DEFAULT FALSE,
"order" INTEGER DEFAULT 100,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP,
UNIQUE (platform_id, name)
);
```
### Migration 3: Enhance Roles Table
```sql
ALTER TABLE roles
ADD COLUMN source_template_id INTEGER REFERENCES platform_role_templates(id),
ADD COLUMN is_custom BOOLEAN DEFAULT FALSE;
```
---
## Implementation Phases
### Phase 1: Data Models (Foundation)
**Files to create:**
- `app/modules/tenancy/models/platform_permission_config.py`
- `app/modules/tenancy/models/platform_role_template.py`
- `migrations/versions/xxx_add_platform_permission_tables.py`
- `migrations/versions/xxx_enhance_roles_table.py`
**Files to modify:**
- `app/modules/tenancy/models/__init__.py` - Export new models
- `app/modules/tenancy/models/vendor.py` - Add Role enhancement
### Phase 2: Service Layer
**Files to create:**
- `app/modules/tenancy/services/platform_permission_service.py`
- `app/modules/tenancy/services/platform_role_template_service.py`
**Files to modify:**
- `app/modules/tenancy/services/vendor_team_service.py` - Add role CRUD, permission validation
### Phase 3: API Endpoints
**Files to create:**
- `app/modules/tenancy/routes/admin/platform_permissions.py`
- `app/modules/tenancy/routes/api/vendor_roles.py`
- `app/modules/tenancy/schemas/platform_permissions.py`
- `app/modules/tenancy/schemas/roles.py`
**Files to modify:**
- `app/modules/tenancy/routes/__init__.py` - Register new routers
### Phase 4: Vendor Onboarding Integration
**Files to modify:**
- `app/modules/tenancy/services/vendor_service.py` - Create default roles from templates during vendor creation
### Phase 5: Admin UI (Optional, Future)
**Files to create/modify:**
- Admin panel for platform permission configuration
- Admin panel for role template management
- Vendor dashboard for custom role management
---
## Verification
1. **App loads:** `python -c "from main import app; print('OK')"`
2. **Migrations run:** `make migrate-up`
3. **Architecture validation:** `python scripts/validate_architecture.py -v`
4. **Unit tests:** Test permission filtering logic
- Platform with no config → all permissions allowed
- Platform with allowed list → only those permissions
- Platform with blocked list → all except blocked
- Tier restrictions → correct subset per tier
5. **Integration tests:**
- Create vendor → gets default roles from platform templates
- Create custom role → validates against platform constraints
- Assign role → user gets correct permissions
- Change tier → available permissions update
6. **API tests:**
- Platform admin can configure permissions
- Vendor owner can create/edit custom roles
- Invalid permissions are rejected
---
## Files Summary
### New Files (9)
| File | Purpose |
|------|---------|
| `app/modules/tenancy/models/platform_permission_config.py` | Platform permission config model |
| `app/modules/tenancy/models/platform_role_template.py` | Platform role template model |
| `app/modules/tenancy/services/platform_permission_service.py` | Platform permission logic |
| `app/modules/tenancy/services/platform_role_template_service.py` | Role template logic |
| `app/modules/tenancy/routes/admin/platform_permissions.py` | Admin API endpoints |
| `app/modules/tenancy/routes/api/vendor_roles.py` | Vendor API endpoints |
| `app/modules/tenancy/schemas/platform_permissions.py` | Pydantic schemas |
| `app/modules/tenancy/schemas/roles.py` | Role schemas |
| `migrations/versions/xxx_platform_permission_tables.py` | Database migration |
### Modified Files (5)
| File | Changes |
|------|---------|
| `app/modules/tenancy/models/__init__.py` | Export new models |
| `app/modules/tenancy/models/vendor.py` | Enhance Role model |
| `app/modules/tenancy/services/vendor_team_service.py` | Add role CRUD, validation |
| `app/modules/tenancy/services/vendor_service.py` | Create default roles on vendor creation |
| `app/modules/tenancy/routes/__init__.py` | Register new routers |
---
## Key Design Decisions
1. **Wildcard support in permissions** - `products.*` matches `products.view`, `products.create`, etc.
2. **Tier inheritance** - Higher tiers include all permissions of lower tiers
3. **Template-based vendor roles** - Default roles created from platform templates, but vendor can customize
4. **Soft validation** - Invalid permissions in existing roles are not automatically removed (audit trail)
5. **Backward compatible** - Existing roles without `source_template_id` continue to work
cl

View File

@@ -31,7 +31,7 @@ The test structure directly mirrors the API code structure:
```
app/api/v1/admin/ → tests/integration/api/v1/admin/
app/api/v1/vendor/ → tests/integration/api/v1/vendor/
app/api/v1/public/ → tests/integration/api/v1/public/
app/api/v1/platform/ → tests/integration/api/v1/platform/
app/api/v1/shared/ → tests/integration/api/v1/shared/
```
@@ -83,7 +83,7 @@ Different teams can work in parallel with fewer conflicts:
```
Admin Team: works in tests/integration/api/v1/admin/
Vendor Team: works in tests/integration/api/v1/vendor/
Public Team: works in tests/integration/api/v1/public/
Public Team: works in tests/integration/api/v1/platform/
```
## Running Tests
@@ -98,7 +98,7 @@ pytest tests/integration/api/v1/vendor/ -v
pytest tests/integration/api/v1/admin/ -v
# All public tests
pytest tests/integration/api/v1/public/ -v
pytest tests/integration/api/v1/platform/ -v
# All shared tests
pytest tests/integration/api/v1/shared/ -v
@@ -277,7 +277,7 @@ See [Vendor API Testing Guide](vendor-api-testing.md) for details.
### Public Tests (`public/`)
Tests for public endpoints at `/api/v1/public/*`:
Tests for public endpoints at `/api/v1/platform/*`:
- Product catalog browsing
- Public vendor profiles