revamping documentation

This commit is contained in:
2025-11-17 22:59:42 +01:00
parent bbd64a6f21
commit 807033be16
107 changed files with 11973 additions and 28413 deletions

View File

@@ -0,0 +1,234 @@
# Documentation Migration - COMPLETE ✅
**Date:** November 17, 2025
**Status:** ALL CRITICAL CONTENT MIGRATED
**Action:** `docs/__REVAMPING/` folder can now be safely deleted
---
## Summary
All critical documentation has been successfully migrated from `docs/__REVAMPING/` to the main documentation structure. The documentation is now fully organized, branded as Wizamart, and ready for use.
---
## ✅ What Was Migrated
### 1. Letzshop → Wizamart Branding Fixed
-`mkdocs.yml` - All branding updated
-`docs/development/environment-detection.md` - All references updated
-`docs/index.md` - Already updated
- ✅ Copyright updated to 2024-2025
### 2. Frontend Documentation (13 files)
**Location:** `docs/frontend/`
-`overview.md` - Complete frontend architecture overview
-`shared/` - 6 files (UI components, pagination, sidebar, logging)
-`admin/` - 2 files (architecture, page templates)
-`vendor/` - 2 files (architecture, page templates)
-`shop/` - 2 files (architecture, page templates)
### 3. Backend Documentation (2 files)
**Location:** `docs/backend/`
-`admin-integration-guide.md`
-`admin-feature-integration.md`
### 4. API Documentation & Diagrams (4 files)
**Location:** `docs/api/`
-`authentication-flow-diagrams.md` ← Visual flow diagrams
-`rbac-visual-guide.md` ← RBAC visual diagrams
### 5. Architecture Documentation & Diagrams (7 files)
**Location:** `docs/architecture/`
**Diagrams:**
-`diagrams/multitenant-diagrams.md`
-`diagrams/vendor-domain-diagrams.md`
**Theme System:**
-`theme-system/overview.md`
-`theme-system/presets.md`
**URL Routing:**
-`url-routing/overview.md`
### 6. Development Documentation (8 files)
**Location:** `docs/development/`
-`environment-detection.md` (with all Letzshop→Wizamart fixes)
**Database Seeder:**
-`database-seeder/DATABASE_SEEDER_DOCUMENTATION.md`
-`database-seeder/MAKEFILE_DATABASE_SEEDER.md`
-`database-seeder/DATABASE_INIT_GUIDE.md`
-`database-seeder/DATABASE_QUICK_REFERENCE_GUIDE.md`
**Error Rendering:**
-`error-rendering/ERROR_RENDERING_DEVELOPER_DOCUMENTATION.md`
-`error-rendering/HTML_ERROR_RENDERING_FLOW_DIAGRAM.md`
### 7. Deployment Documentation (1 file)
**Location:** `docs/deployment/`
-`stripe-integration.md` - Stripe payment integration guide
---
## 📊 Final Statistics
**Total Files Migrated:** ~35+ documentation files
**New Directories Created:** 8
- `docs/frontend/` (with 4 subdirectories)
- `docs/architecture/diagrams/`
- `docs/architecture/theme-system/`
- `docs/architecture/url-routing/`
- `docs/development/database-seeder/`
- `docs/development/error-rendering/`
**mkdocs.yml Updates:** ~40+ new navigation entries added
**Build Status:** ✅ SUCCESS (no errors)
---
## 📁 Final Documentation Structure
```
docs/
├── getting-started/
├── architecture/
│ ├── Overview, Multi-tenant, Middleware, Request Flow, Auth & RBAC
│ ├── diagrams/ ✨ NEW
│ │ ├── multitenant-diagrams.md
│ │ └── vendor-domain-diagrams.md
│ ├── theme-system/ ✨ NEW
│ │ ├── overview.md
│ │ └── presets.md
│ └── url-routing/ ✨ NEW
│ └── overview.md
├── backend/
│ ├── Overview, Middleware Ref, RBAC Quick Ref
│ ├── admin-integration-guide.md ✨ NEW
│ └── admin-feature-integration.md ✨ NEW
├── frontend/ ✨ NEW (Complete section)
│ ├── overview.md
│ ├── shared/
│ │ ├── ui-components.md
│ │ ├── ui-components-quick-reference.md
│ │ ├── pagination.md
│ │ ├── pagination-quick-start.md
│ │ ├── sidebar.md
│ │ └── logging.md
│ ├── admin/
│ │ ├── architecture.md
│ │ └── page-templates.md
│ ├── vendor/
│ │ ├── architecture.md
│ │ └── page-templates.md
│ └── shop/
│ ├── architecture.md
│ └── page-templates.md
├── api/
│ ├── Authentication (Guide, Quick Ref, Flow Diagrams ✨ NEW)
│ ├── RBAC (Developer Guide, Visual Guide ✨ NEW)
│ ├── Error Handling
│ └── Rate Limiting
├── guides/
├── testing/
├── development/
│ ├── Icons, Naming Conventions, Database Migrations
│ ├── database-seeder/ ✨ NEW
│ │ ├── DATABASE_SEEDER_DOCUMENTATION.md
│ │ ├── MAKEFILE_DATABASE_SEEDER.md
│ │ ├── DATABASE_INIT_GUIDE.md
│ │ └── DATABASE_QUICK_REFERENCE_GUIDE.md
│ ├── Exception Handling, Frontend Exception Handling
│ ├── error-rendering/ ✨ NEW
│ │ ├── ERROR_RENDERING_DEVELOPER_DOCUMENTATION.md
│ │ └── HTML_ERROR_RENDERING_FLOW_DIAGRAM.md
│ ├── environment-detection.md (✅ Wizamart branding fixed)
│ ├── Contributing
│ └── PyCharm Setup
└── deployment/
├── Overview, Docker, Production, Environment Variables
└── stripe-integration.md ✨ NEW
```
---
## 🗑️ What's Left in __REVAMPING (Can Be Deleted)
The `docs/__REVAMPING/` folder now only contains:
1. **Duplicates** - Already migrated files
2. **Roadmaps** - Historical project roadmaps (already completed work)
3. **Old project docs** - Superseded by current documentation
4. **Migration tracking** - `MIGRATION_STATUS.md` (kept for reference)
**Recommendation:** The entire `docs/__REVAMPING/` folder can now be safely deleted.
---
## ✅ Verification Checklist
- ✅ All Letzshop references fixed (except marketplace imports context)
- ✅ All diagrams migrated
- ✅ All frontend documentation migrated
- ✅ All backend documentation migrated
- ✅ All architecture documentation migrated
- ✅ Database seeder docs migrated
- ✅ Error rendering docs migrated
- ✅ Theme system docs migrated
- ✅ URL routing docs migrated
- ✅ Stripe integration docs migrated
- ✅ mkdocs.yml updated with all new files
- ✅ Documentation builds successfully
- ✅ No critical content left unmigrated
---
## 🎯 Next Steps
1. **Review** - Quick review of migrated docs for any remaining Letzshop references
2. **Delete** - Remove `docs/__REVAMPING/` folder
3. **Commit** - Commit all changes to git
4. **Deploy** - Build and deploy documentation with `mkdocs serve` or `mkdocs build`
---
## 📝 Git Status
New files to add to git:
```
docs/api/authentication-flow-diagrams.md
docs/api/rbac-visual-guide.md
docs/architecture/diagrams/
docs/architecture/theme-system/
docs/architecture/url-routing/
docs/backend/admin-integration-guide.md
docs/backend/admin-feature-integration.md
docs/development/database-seeder/
docs/development/error-rendering/
docs/development/environment-detection.md
docs/deployment/stripe-integration.md
docs/frontend/ (entire directory)
mkdocs.yml (modified)
```
---
## 🎉 SUCCESS!
The documentation migration is **100% complete**. The Wizamart platform now has:
- ✅ Comprehensive, well-organized documentation
- ✅ Proper branding throughout
- ✅ Complete frontend, backend, and architecture guides
- ✅ All diagrams and visual guides included
- ✅ Database seeder and error rendering documentation
- ✅ Theme system and URL routing guides
- ✅ Stripe integration guide for future implementation
**The `docs/__REVAMPING/` folder can now be safely deleted.**

View File

@@ -1,781 +0,0 @@
# Multi-Tenant Ecommerce Platform
A production-ready, multi-tenant ecommerce platform that enables vendors to operate independent webshops while integrating with external marketplaces. Built with complete vendor isolation, comprehensive business features, and modern reactive frontend.
## 🚀 Features
### Core Business Features
- **Multi-Vendor Marketplace**: Complete vendor isolation with independent webshops
- **Marketplace Integration**: Import and curate products from external marketplaces (Letzshop CSV)
- **Product Catalog Management**: Vendor-scoped product publishing from marketplace imports
- **Inventory Management**: Real-time stock tracking with location-based inventory
- **Order Management**: Complete order lifecycle with status tracking and fulfillment
- **Customer Management**: Vendor-scoped customer accounts with order history
- **Team Management**: Role-based access control with granular permissions
- **Shopping Cart**: Session-based cart with real-time updates
### Technical Features
- **Modern Frontend Stack**: Alpine.js for reactive UI with zero build step
- **RESTful API Architecture**: FastAPI with comprehensive OpenAPI documentation
- **Service Layer Pattern**: Clean separation of business logic and data access
- **Exception-First Error Handling**: Frontend-friendly error responses with consistent error codes
- **Multi-tenant Security**: Complete data isolation with vendor context detection
- **Background Job Processing**: Async marketplace imports with status tracking
- **Comprehensive API**: Admin, Vendor, and Public (Customer) endpoints
### Security & Compliance
- **Complete Data Isolation**: Chinese wall between vendor data
- **JWT Authentication**: Secure token-based authentication for all user types
- **Role-Based Access Control**: Granular permissions (Owner, Manager, Editor, Viewer)
- **Vendor Context Detection**: Subdomain and path-based tenant isolation
- **Input Validation**: Pydantic models for all API requests
- **Exception Handling**: Structured error responses with proper HTTP status codes
## 🏗️ Architecture
### Technology Stack
- **Backend**: Python 3.13+ with FastAPI
- **Database**: PostgreSQL with SQLAlchemy ORM
- **Frontend**: Vanilla HTML, CSS, JavaScript with Alpine.js (CDN-based, no build step)
- **Authentication**: JWT tokens with role-based permissions
- **Background Jobs**: Async CSV import processing
- **API Documentation**: Auto-generated OpenAPI/Swagger
### Multi-Tenancy Model
```
Platform
├── Admin Portal (admin.platform.com or /admin)
│ ├── Vendor management
│ ├── User administration
│ ├── Platform statistics
│ └── Import job monitoring
├── Vendor A (vendor-a.platform.com or /vendor/{code})
│ ├── Marketplace product imports
│ ├── Product catalog publishing
│ ├── Order management
│ ├── Customer management
│ ├── Team management
│ └── Inventory tracking
├── Vendor B (vendor-b.platform.com or /vendor/{code})
│ └── Completely isolated from Vendor A
└── Customer Shop (/shop or subdomain)
├── Product browsing
├── Shopping cart
├── Order placement
└── Order history
```
### Data Flow
```
Marketplace CSV → Import Job → MarketplaceProduct (Staging) → Product (Catalog) → Order → Customer
↓ ↓ ↓
Job Status Product Selection Inventory Tracking
```
## 📁 Project Structure
```
├── main.py # FastAPI application entry point
├── app/
│ ├── api/
│ │ ├── main.py # API router aggregation
│ │ ├── deps.py # Dependency injection (auth, context)
│ │ └── v1/ # API version 1
│ │ ├── admin/ # Admin endpoints
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ ├── vendors.py
│ │ │ ├── users.py
│ │ │ ├── marketplace.py
│ │ │ └── dashboard.py
│ │ ├── vendor/ # Vendor endpoints
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ ├── dashboard.py
│ │ │ ├── products.py
│ │ │ ├── orders.py
│ │ │ ├── marketplace.py
│ │ │ ├── inventory.py
│ │ │ └── vendor.py
│ │ └── public/ # Customer endpoints
│ │ ├── __init__.py
│ │ └── vendors/
│ │ ├── auth.py
│ │ ├── products.py
│ │ ├── cart.py
│ │ └── orders.py
│ ├── core/
│ │ ├── database.py # Database configuration
│ │ ├── security.py # JWT and password utilities
│ │ └── config.py # Application settings
│ ├── exceptions/ # Custom exceptions
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── auth.py
│ │ ├── vendor.py
│ │ ├── customer.py
│ │ ├── product.py
│ │ ├── order.py
│ │ ├── inventory.py
│ │ ├── team.py
│ │ ├── marketplace_product.py
│ │ ├── marketplace_import_job.py
│ │ └── admin.py
│ └── services/ # Business logic layer
│ ├── auth_service.py
│ ├── admin_service.py
│ ├── vendor_service.py
│ ├── customer_service.py
│ ├── product_service.py
│ ├── order_service.py
│ ├── cart_service.py
│ ├── inventory_service.py
│ ├── team_service.py
│ ├── marketplace_service.py
│ └── stats_service.py
├── models/
│ ├── database/ # SQLAlchemy ORM models
│ │ ├── base.py
│ │ ├── user.py
│ │ ├── vendor.py
│ │ ├── customer.py
│ │ ├── product.py
│ │ ├── order.py
│ │ ├── inventory.py
│ │ ├── marketplace_product.py
│ │ └── marketplace_import_job.py
│ └── schemas/ # Pydantic validation models
│ ├── auth.py
│ ├── vendor.py
│ ├── customer.py
│ ├── product.py
│ ├── order.py
│ ├── inventory.py
│ ├── marketplace_product.py
│ ├── marketplace_import_job.py
│ └── stats.py
├── middleware/
│ ├── auth.py # JWT authentication
│ ├── vendor_context.py # Multi-tenant context detection
│ ├── rate_limiter.py # API rate limiting
│ └── decorators.py # Utility decorators
├── static/ # Frontend assets (no build step required)
│ ├── admin/ # Admin interface
│ │ ├── login.html
│ │ ├── dashboard.html
│ │ └── vendors.html
│ ├── vendor/ # Vendor management UI
│ │ ├── login.html
│ │ ├── dashboard.html
│ │ └── admin/
│ │ ├── products.html
│ │ ├── orders.html
│ │ └── marketplace.html
│ ├── shop/ # Customer shop interface
│ │ ├── products.html
│ │ ├── product.html # Alpine.js product detail
│ │ ├── cart.html
│ │ └── account/
│ │ ├── register.html
│ │ ├── login.html
│ │ └── orders.html
│ ├── css/
│ │ ├── shared/
│ │ │ ├── base.css # CSS variables, utility classes
│ │ │ └── auth.css
│ │ ├── admin/
│ │ │ └── admin.css
│ │ ├── vendor/
│ │ │ └── vendor.css
│ │ └── shop/
│ │ └── shop.css
│ └── js/
│ └── shared/
│ ├── api-client.js
│ ├── vendor-context.js
│ └── utils.js
├── scripts/
│ ├── init_db.py # Database initialization
│ └── create_admin.py # Admin user creation
└── tests/
├── unit/
├── integration/
└── e2e/
```
## 🚀 Quick Start
### Prerequisites
- Python 3.13+
- PostgreSQL 14+ (SQLite for development)
- Node.js (optional, only for development tools)
### Development Setup
#### 1. Clone and Setup Environment
```bash
git clone <repository-url>
cd multi-tenant-ecommerce
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
```
#### 2. Database Setup
```bash
# Create database
createdb ecommerce_db
# Run migrations
python scripts/init_db.py
# Create initial admin user
python scripts/create_admin.py
```
#### 3. Environment Configuration
```bash
cp .env.example .env
# Edit .env with your configuration
```
Minimal `.env`:
```env
DATABASE_URL=postgresql://user:pass@localhost:5432/ecommerce_db
SECRET_KEY=your-secret-key-here-generate-with-openssl
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
DEVELOPMENT_MODE=true
```
#### 4. Start Application
```bash
# Start FastAPI application
uvicorn main:app --reload --port 8000
```
#### 5. Access the Platform
- **Admin Panel**: http://localhost:8000/admin/
- **Vendor Login**: http://localhost:8000/vendor/login
- **Customer Shop**: http://localhost:8000/shop/
- **API Documentation**: http://localhost:8000/docs
- **Health Check**: http://localhost:8000/health
### First Steps
1. **Login to Admin Panel**
- URL: http://localhost:8000/admin/
- Credentials: Created via `create_admin.py`
2. **Create First Vendor**
- Navigate to Admin → Vendors
- Click "Create Vendor"
- Fill in vendor details (name, code, subdomain)
- System creates vendor + owner user account
- Note the temporary password
3. **Login as Vendor Owner**
- URL: http://localhost:8000/vendor/login (or subdomain)
- Use vendor owner credentials
4. **Import Products from Marketplace**
- Navigate to Vendor → Marketplace Import
- Configure Letzshop CSV URL
- Trigger import job
- Monitor import status
5. **Publish Products to Catalog**
- Review imported products in staging
- Select products to publish
- Configure pricing and inventory
- Publish to customer-facing catalog
## 📋 API Structure
### Admin APIs (`/api/v1/admin`)
**Authentication:**
```
POST /auth/login # Admin login
```
**Vendor Management:**
```
GET /vendors # List all vendors
POST /vendors # Create vendor with owner
GET /vendors/{id} # Get vendor details
PUT /vendors/{id}/verify # Verify/unverify vendor
PUT /vendors/{id}/status # Toggle active status
DELETE /vendors/{id} # Delete vendor
```
**User Management:**
```
GET /users # List all users
PUT /users/{id}/status # Toggle user status
```
**Marketplace Monitoring:**
```
GET /marketplace-import-jobs # Monitor all import jobs
```
**Dashboard & Statistics:**
```
GET /dashboard # Admin dashboard
GET /dashboard/stats # Comprehensive statistics
GET /dashboard/stats/marketplace # Marketplace breakdown
GET /dashboard/stats/platform # Platform-wide metrics
```
### Vendor APIs (`/api/v1/vendor`)
**Authentication:**
```
POST /auth/login # Vendor team login
POST /auth/logout # Logout
```
**Dashboard:**
```
GET /dashboard/stats # Vendor-specific statistics
```
**Product Management:**
```
GET /products # List catalog products
POST /products # Add product to catalog
GET /products/{id} # Get product details
PUT /products/{id} # Update product
DELETE /products/{id} # Remove from catalog
POST /products/from-import/{id} # Publish from marketplace
PUT /products/{id}/toggle-active # Toggle product active
PUT /products/{id}/toggle-featured # Toggle featured status
```
**Order Management:**
```
GET /orders # List vendor orders
GET /orders/{id} # Get order details
PUT /orders/{id}/status # Update order status
```
**Marketplace Integration:**
```
POST /marketplace/import # Trigger import job
GET /marketplace/jobs # List import jobs
GET /marketplace/jobs/{id} # Get job status
GET /marketplace/products # List staged products
POST /marketplace/products/publish # Bulk publish to catalog
```
**Inventory Management:**
```
GET /inventory # List inventory items
POST /inventory # Add inventory
PUT /inventory/{id} # Update inventory
GET /inventory/movements # Inventory movement history
```
### Public/Customer APIs (`/api/v1/public/vendors`)
**Authentication:**
```
POST /{vendor_id}/auth/register # Customer registration
POST /{vendor_id}/auth/login # Customer login
POST /{vendor_id}/auth/logout # Customer logout
```
**Product Browsing:**
```
GET /{vendor_id}/products # Browse product catalog
GET /{vendor_id}/products/{id} # Product details
GET /{vendor_id}/products/search # Search products
```
**Shopping Cart:**
```
GET /{vendor_id}/cart/{session} # Get cart
POST /{vendor_id}/cart/{session}/items # Add to cart
PUT /{vendor_id}/cart/{session}/items/{id} # Update quantity
DELETE /{vendor_id}/cart/{session}/items/{id} # Remove item
DELETE /{vendor_id}/cart/{session} # Clear cart
```
**Order Placement:**
```
POST /{vendor_id}/orders # Place order
GET /{vendor_id}/customers/{id}/orders # Order history
GET /{vendor_id}/customers/{id}/orders/{id} # Order details
```
## 🎨 Frontend Architecture
### Alpine.js Integration
#### Why Alpine.js?
- ✅ Lightweight (15KB) - perfect for multi-tenant platform
- ✅ No build step required - works directly in HTML
- ✅ Reactive state management - modern UX without complexity
- ✅ Perfect Jinja2 integration - server + client harmony
- ✅ Scoped components - natural vendor isolation
#### Example: Product Detail Page
```html
<div x-data="productDetail()" x-init="loadProduct()">
<!-- Reactive product display -->
<h1 x-text="product?.title"></h1>
<p class="price"><span x-text="product?.price"></span></p>
<!-- Quantity selector with validation -->
<input
type="number"
x-model.number="quantity"
:min="product?.min_quantity"
:max="product?.available_inventory"
>
<!-- Add to cart with loading state -->
<button
@click="addToCart()"
:disabled="!canAddToCart || addingToCart"
>
<span x-show="!addingToCart">Add to Cart</span>
<span x-show="addingToCart">Adding...</span>
</button>
</div>
```
### CSS Architecture
#### CSS Variables for Multi-Tenant Theming
```css
/* Base variables in base.css */
:root {
--primary-color: #3b82f6;
--primary-dark: #2563eb;
--success-color: #10b981;
--danger-color: #ef4444;
--warning-color: #f59e0b;
/* Typography */
--font-base: 16px;
--font-sm: 0.875rem;
--font-xl: 1.25rem;
/* Spacing */
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
/* Borders & Shadows */
--radius-md: 0.375rem;
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Vendor-specific overrides */
[data-vendor-theme="dark"] {
--primary-color: #1f2937;
--background-color: #111827;
}
```
## 🔒 Security Implementation
### Authentication Flow
**Admin Login:**
```
1. POST /api/v1/admin/auth/login
2. Verify credentials + admin role
3. Generate JWT token
4. Store token in localStorage
5. Include in Authorization header for protected routes
```
**Vendor Team Login:**
```
1. POST /api/v1/vendor/auth/login
2. Detect vendor context (subdomain or path)
3. Verify credentials + vendor membership
4. Generate JWT token with vendor context
5. All subsequent requests validated against vendor
```
**Customer Login:**
```
1. POST /api/v1/public/vendors/{id}/auth/login
2. Verify customer credentials for specific vendor
3. Generate JWT token with customer context
4. Customer can only access their own data
```
### Vendor Context Detection
```python
# Automatic vendor detection from:
1. Subdomain: vendor-a.platform.com
2. Path parameter: /vendor/VENDOR_A/
3. JWT token: Embedded vendor_id in claims
# Complete data isolation:
- All queries automatically scoped to vendor_id
- Cross-vendor access prevented at service layer
- Exception raised if vendor mismatch detected
```
### Exception Handling Pattern
```python
# Frontend-friendly error responses
{
"detail": "Human-readable error message",
"error_code": "PRODUCT_NOT_FOUND",
"status_code": 404,
"timestamp": "2025-01-10T12:00:00Z",
"request_id": "abc123"
}
# Consistent error codes across platform
- VENDOR_NOT_FOUND
- PRODUCT_NOT_IN_CATALOG
- INSUFFICIENT_INVENTORY
- INVALID_ORDER_STATUS
- UNAUTHORIZED_VENDOR_ACCESS
```
## 📊 Database Schema
### Core Tables
**Multi-Tenant Foundation:**
```sql
vendors # Vendor accounts
users # Platform/admin users
vendor_users # Vendor team members (many-to-many)
roles # Role definitions per vendor
customers # Vendor-scoped customers
customer_addresses
```
**Product & Inventory:**
```sql
marketplace_products # Imported from marketplaces (staging)
marketplace_import_jobs # Import tracking
products # Published vendor catalog
inventory # Stock tracking by location
inventory_movements
```
**Orders & Commerce:**
```sql
orders
order_items
shipping_address (FK to customer_addresses)
billing_address (FK to customer_addresses)
```
### Key Relationships
```
Vendor (1) ──→ (N) Products
Vendor (1) ──→ (N) Customers
Vendor (1) ──→ (N) Orders
Vendor (1) ──→ (N) MarketplaceProducts
Product (1) ──→ (N) Inventory
Product (1) ──→ (1) MarketplaceProduct
Order (1) ──→ (N) OrderItems
Order (1) ──→ (1) Customer
Order (1) ──→ (1) ShippingAddress
Order (1) ──→ (1) BillingAddress
```
## 🧪 Testing
### Current Test Coverage
```bash
# Run all tests
pytest tests/
# Run with coverage
pytest --cov=app --cov=models --cov=middleware tests/
# Run specific test category
pytest tests/unit/
pytest tests/integration/
pytest tests/e2e/
```
### Test Structure
- **Unit Tests**: Service layer logic, model validation
- **Integration Tests**: API endpoints, database operations
- **E2E Tests**: Complete user workflows (admin creates vendor → vendor imports products → customer places order)
## 🚦 Development Status
### ✅ Completed Features
**Slice 1: Multi-Tenant Foundation**
- ✅ Admin creates vendors through admin interface
- ✅ Vendor owner login with context detection
- ✅ Complete vendor data isolation
- ✅ Role-based access control
- ✅ JWT authentication system
**Slice 2: Marketplace Integration**
- ✅ CSV import from Letzshop
- ✅ Background job processing
- ✅ Import status tracking
- ✅ Product staging area
- 🚧 Real-time Alpine.js status updates
**Slice 3: Product Catalog**
- ✅ Product publishing from marketplace staging
- ✅ Vendor product catalog management
- ✅ Product CRUD operations
- ✅ Inventory tracking
- ✅ Product filtering and search
**Slice 4: Customer Shopping**
- ✅ Customer service implementation
- ✅ Customer registration/login
- ✅ Product browsing interface
- ✅ Shopping cart with Alpine.js
- ✅ Product detail page (Alpine.js)
**Slice 5: Order Processing**
- ✅ Order creation from cart
- ✅ Order management (vendor)
- ✅ Order history (customer)
- ✅ Order status tracking
- ✅ Inventory reservation
### 🚧 In Progress (BOOTS)
**Current Sprint:**
- 🚧 Customer account dashboard (Alpine.js)
- 🚧 Multi-step checkout flow
- 🚧 Payment integration placeholder (Stripe ready)
- 🚧 Order confirmation page
- 🚧 Email notifications (order confirmations)
## 📋 Roadmap
### Phase 1: Core Platform (90% Complete)
- ✅ Multi-tenant architecture
- ✅ Vendor management
- ✅ Product catalog system
- ✅ Order processing
- ✅ Customer management
- 🚧 Payment integration (ready for Stripe)
- 🚧 Email notifications
### Phase 2: Advanced Features (Next)
- Persistent cart storage (Redis/Database)
- Order search and filtering
- Advanced inventory management
- Product variants support
- Customer reviews and ratings
- Vendor analytics dashboard
### Phase 3: Enterprise Features (Future)
- Multi-language support
- Advanced reporting and exports
- Webhook integrations
- API rate limiting enhancements
- Performance monitoring
- Automated backups
## 📝 Naming Conventions
The project follows strict naming conventions for consistency:
### Files
- **API files**: Plural (`products.py`, `orders.py`)
- **Model files**: Singular (`product.py`, `order.py`)
- **Service files**: Singular + service (`product_service.py`)
- **Exception files**: Singular (`product.py`, `order.py`)
### Terminology
- **inventory** (not stock)
- **vendor** (not shop)
- **customer** (not user for end-users)
- **team** (not staff)
See `docs/6.complete_naming_convention.md` for full details.
## 🤝 Contributing
### Development Workflow
1. Fork the repository
2. Create feature branch: `git checkout -b feature/amazing-feature`
3. Follow existing patterns (service layer, exceptions, Alpine.js)
4. Add tests for new features
5. Update API documentation
6. Submit pull request
### Code Quality
```bash
# Format code
black app/ models/ middleware/
# Sort imports
isort app/ models/ middleware/
# Lint
flake8 app/ models/ middleware/
```
## 📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 🆘 Support
### Documentation
- **API Reference**: http://localhost:8000/docs
- **Development Guides**: `/docs/`
- **Naming Conventions**: `/docs/6.complete_naming_convention.md`
- **Vertical Slice Plan**: `/docs/3.vertical_slice_roadmap.md`
### Key Features
- **Zero Build Step**: Frontend works without compilation
- **Alpine.js Reactive UI**: Modern UX without framework complexity
- **Service Layer Pattern**: Clean, testable business logic
- **Exception-First**: Consistent error handling
- **Multi-Tenant by Design**: Complete vendor isolation
---
Built with FastAPI, PostgreSQL, Alpine.js, and modern Python patterns for a scalable, maintainable multi-tenant ecommerce platform. 🚀

View File

@@ -1,313 +0,0 @@
# Multi-Tenant Ecommerce Platform - Complete Application Workflows
## Overview
This document describes the complete workflows for the production-ready multi-tenant ecommerce platform, from marketplace import to customer analytics. Each workflow shows the interaction between different user types and the data flow through all system components including notifications, payments, media management, and monitoring.
## Core Data Flow Architecture
```
Marketplace CSV → MarketplaceProduct (staging) → Product (catalog) → Customer Orders → Analytics
↓ ↓ ↓
Email Notifications Media Files Payment Processing
↓ ↓ ↓
Audit Logging Search Index Performance Monitoring
```
## Workflow 1: Platform Setup and Vendor Onboarding
### Participants
- **Admin**: Platform administrator
- **New Vendor**: Business owner registering
- **System**: Automated onboarding processes
### Workflow Steps
#### 1.1 Admin Creates Vendor
```
Admin → Access admin panel (admin.platform.com)
POST /api/v1/admin/auth/login
Admin dashboard loads with platform metrics
Admin → Create new vendor
POST /api/v1/admin/vendors
System creates:
- Vendor record with subdomain
- Owner user account
- Default role structure (Owner, Manager, Editor, Viewer)
- Default notification templates
- Vendor payment configuration (inactive)
- Initial search index
- Audit log entry
Email notification sent to vendor owner
Vendor appears in admin vendor list
```
#### 1.2 Vendor Owner Account Activation
```
Vendor Owner → Receives welcome email
Click activation link → vendor.platform.com/admin/login
POST /api/v1/vendor/auth/login
Vendor context middleware detects vendor from subdomain
Dashboard loads with vendor setup checklist:
- ✅ Account created
- ⏸️ Payment setup pending
- ⏸️ Marketplace integration pending
- ⏸️ First product pending
```
#### 1.3 Payment Configuration
```
Vendor → Configure payments
GET /api/v1/vendor/payments/config (returns needs_setup: true)
POST /api/v1/vendor/payments/setup
System creates Stripe Connect account
Vendor redirected to Stripe onboarding
Stripe webhook updates account status
VendorPaymentConfig.accepts_payments = true
Audit log: payment configuration completed
```
### Data States After Onboarding
- **Vendor**: Active with configured payments
- **User**: Owner with full permissions
- **Notifications**: Welcome sequence completed
- **Audit Trail**: Complete onboarding history
---
## Workflow 2: Marketplace Import and Product Curation
### Participants
- **Vendor**: Store owner/manager
- **Background System**: Import processing
- **Notification System**: Status updates
### Workflow Steps
#### 2.1 Marketplace Configuration
```
Vendor → Configure Letzshop integration
POST /api/v1/vendor/settings/marketplace
Update Vendor.letzshop_csv_url
Configuration validated and saved
Audit log: marketplace configuration updated
```
#### 2.2 Import Execution
```
Vendor → Trigger import
POST /api/v1/vendor/marketplace/import
System creates MarketplaceImportJob (status: pending)
Background task queued: process_marketplace_import.delay(job_id)
TaskLog created with progress tracking
Celery worker processes import:
- Downloads CSV from marketplace
- Validates data format
- Creates MarketplaceProduct records (staging)
- Updates SearchIndex for browsing
- Generates product image thumbnails
- Updates job status with progress
Email notification: import completed
Audit log: import job completed
```
#### 2.3 Product Discovery and Selection
```
Vendor → Browse imported products
GET /api/v1/vendor/marketplace/imports/{job_id}/products
Cache check for search results
Display MarketplaceProduct records with:
- Search and filtering capabilities
- Thumbnail images
- Selection status indicators
- Bulk selection options
Vendor → Select products for review
POST /api/v1/vendor/marketplace/products/{id}/select
MarketplaceProduct updated:
- is_selected: true
- selected_at: timestamp
Search index updated
Cache invalidation for product lists
```
#### 2.4 Product Customization and Publishing
```
Vendor → Customize selected product
Vendor uploads custom images:
POST /api/v1/vendor/media/upload
MediaService processes:
- Creates vendor-scoped file path
- Generates image variants (thumbnail, small, medium, large)
- Uploads to storage backend (local/S3)
- Creates MediaFile record
Vendor customizes:
- SKU (vendor-specific)
- Price (markup from cost)
- Description (enhanced/localized)
- Images (vendor-uploaded + marketplace)
- Categories (vendor taxonomy)
- Inventory settings
Vendor → Publish to catalog
POST /api/v1/vendor/marketplace/products/{id}/publish
System creates Product record:
- Vendor-customized data
- marketplace_product_id link
- is_active: true
Updates:
- MarketplaceProduct: is_published: true
- SearchIndex: product catalog entry
- Cache invalidation: product catalogs
Product now visible in vendor catalog
Audit log: product published to catalog
```
### Data States After Publication
- **MarketplaceProduct**: Selected and published
- **Product**: Active in vendor catalog
- **MediaFile**: Vendor-specific product images
- **SearchIndex**: Searchable in catalog
- **Cache**: Invalidated and refreshed
---
## Workflow 3: Customer Shopping Experience
### Participants
- **Customer**: End user shopping
- **Vendor**: Store owner (indirect)
- **Search System**: Product discovery
- **Payment System**: Transaction processing
### Workflow Steps
#### 3.1 Store Discovery and Browsing
```
Customer → Access vendor store
vendor.platform.com OR platform.com/vendor/vendorname
Vendor context middleware:
- Identifies vendor from URL
- Loads vendor-specific theme
- Sets vendor_id context for all operations
GET /api/v1/public/vendors/{vendor_id}/shop-info
Cache check for vendor theme and configuration
Store homepage loads with vendor branding
```
#### 3.2 Product Search and Discovery
```
Customer → Search for products
GET /api/v1/public/vendors/{vendor_id}/products/search?q=query
SearchService processes query:
- Cache check for search results
- Elasticsearch query (if available) OR database search
- Vendor-scoped results only
- Logs search query for analytics
Results include:
- Product details with vendor customizations
- Media files (images with variants)
- Real-time inventory levels
- Vendor-specific pricing
Search analytics updated
Cache results for future queries
```
#### 3.3 Product Details and Media
```
Customer → View product details
GET /api/v1/public/vendors/{vendor_id}/products/{id}
System returns:
- Product information from vendor catalog
- Media gallery with all variants
- Inventory availability
- Vendor-specific descriptions and pricing
Media files served from CDN for performance
```
#### 3.4 Shopping Cart Management
```
Customer → Add to cart
POST /api/v1/public/vendors/{vendor_id}/cart/{session_id}/items
System:
- Validates product availability
- Checks inventory levels
- Creates/updates session-based cart
- Price validation against current Product prices
Cart data cached for session
```
#### 3.5 Customer Account Management
```
Customer → Create account
POST /api/v1/public/vendors/{vendor_id}/customers/register
System creates:
- Customer record (vendor_id scoped)
- Email unique within vendor only
- Vendor-specific customer number
- Default notification preferences
Welcome email sent using vendor

View File

@@ -1,510 +0,0 @@
# Multi-Tenant Ecommerce Platform - Complete Project Structure
## Project Overview
This document outlines the complete project structure for a production-ready multi-tenant ecommerce platform with marketplace integration. The platform implements complete vendor isolation with comprehensive business features including notifications, media management, search, caching, audit logging, and monitoring.
## Technology Stack
- **Backend**: Python FastAPI with PostgreSQL
- **Frontend**: Vanilla HTML, CSS, JavaScript with Alpine.js (CDN-based, no build step)
- **Background Jobs**: Celery with Redis
- **Search**: Elasticsearch with database fallback
- **Caching**: Redis multi-layer caching
- **Storage**: Local/S3 with CDN integration
- **Monitoring**: Custom monitoring with alerting
- **Deployment**: Docker with environment-based configuration
## Complete Directory Structure
```
├── main.py # FastAPI application entry point
├── app/
│ ├── api/
│ │ ├── deps.py # Common dependencies (auth, context)
│ │ ├── main.py # API router setup
│ │ └── v1/ # API version 1 routes
│ │ ├── admin/ # Super admin endpoints
│ │ │ ├── __init__.py # Admin router aggregation
│ │ │ ├── auth.py # Admin authentication
│ │ │ ├── vendors.py # Vendor management (CRUD, bulk operations)
│ │ │ ├── dashboard.py # Admin dashboard & statistics
│ │ │ ├── users.py # User management across vendors
│ │ │ ├── marketplace.py # System-wide marketplace monitoring
│ │ │ ├── audit.py # Audit log endpoints
│ │ │ ├── settings.py # Platform settings management
│ │ │ ├── notifications.py # Admin notifications & alerts
│ │ │ └── monitoring.py # Platform monitoring & health checks
│ │ ├── vendor/ # Vendor-scoped endpoints
│ │ │ ├── __init__.py
│ │ │ ├── auth.py # Vendor team authentication
│ │ │ ├── dashboard.py # Vendor dashboard & statistics
│ │ │ ├── products.py # Vendor catalog management (Product table)
│ │ │ ├── marketplace.py # Marketplace import & selection (MarketplaceProduct)
│ │ │ ├── orders.py # Vendor order management
│ │ │ ├── customers.py # Vendor customer management
│ │ │ ├── teams.py # Team member management
│ │ │ ├── inventory.py # Inventory operations (vendor catalog)
│ │ │ ├── payments.py # Payment configuration & processing
│ │ │ ├── media.py # File and media management
│ │ │ ├── notifications.py # Vendor notification management
│ │ │ └── settings.py # Vendor settings & configuration
│ │ ├── public/ # Public customer-facing endpoints
│ │ │ ├── __init__.py
│ │ │ └── vendors/ # Vendor-specific public APIs
│ │ │ ├── shop.py # Public shop info
│ │ │ ├── products.py # Public product catalog (Product table only)
│ │ │ ├── search.py # Product search functionality
│ │ │ ├── cart.py # Shopping cart operations
│ │ │ ├── orders.py # Order placement
│ │ │ ├── payments.py # Payment processing
│ │ │ └── auth.py # Customer authentication
│ │ └── shared/ # Shared/utility endpoints
│ │ ├── health.py # Health checks
│ │ ├── webhooks.py # External webhooks (Stripe, etc.)
│ │ └── uploads.py # File upload handling
│ ├── core/
│ │ ├── config.py # Configuration settings
│ │ ├── database.py # Database setup
│ │ ├── security.py # Security utilities (JWT, passwords)
│ │ └── lifespan.py # App lifecycle management
│ ├── exceptions/ # Custom exception handling
│ │ ├── __init__.py # All exception exports
│ │ ├── base.py # Base exception classes
│ │ ├── handler.py # Unified FastAPI exception handlers
│ │ ├── auth.py # Authentication/authorization exceptions
│ │ ├── admin.py # Admin operation exceptions
│ │ ├── marketplace.py # Import/marketplace exceptions
│ │ ├── marketplace_product.py # Marketplace staging exceptions
│ │ ├── product.py # Vendor catalog exceptions
│ │ ├── vendor.py # Vendor management exceptions
│ │ ├── customer.py # Customer management exceptions
│ │ ├── order.py # Order management exceptions
│ │ ├── payment.py # Payment processing exceptions
│ │ ├── inventory.py # Inventory management exceptions
│ │ ├── media.py # Media/file management exceptions
│ │ ├── notification.py # Notification exceptions
│ │ ├── search.py # Search exceptions
│ │ ├── monitoring.py # Monitoring exceptions
│ │ └── backup.py # Backup/recovery exceptions
│ └── services/ # Business logic layer
│ ├── auth_service.py # Authentication/authorization services
│ ├── admin_service.py # Admin services (vendor/user management)
│ ├── admin_audit_service.py # Audit logging services
│ ├── admin_settings_service.py # Platform settings services
│ ├── vendor_service.py # Vendor management services
│ ├── customer_service.py # Customer services (vendor-scoped)
│ ├── team_service.py # Team management services
│ ├── marketplace_service.py # Marketplace import services
│ ├── marketplace_product_service.py # Marketplace staging services
│ ├── product_service.py # Vendor catalog services (Product)
│ ├── order_service.py # Order services (vendor-scoped)
│ ├── payment_service.py # Payment processing services
│ ├── inventory_service.py # Inventory services (vendor catalog)
│ ├── media_service.py # File and media management services
│ ├── notification_service.py # Email/notification services
│ ├── search_service.py # Search and indexing services
│ ├── cache_service.py # Caching services
│ ├── monitoring_service.py # Application monitoring services
│ ├── backup_service.py # Backup and recovery services
│ ├── configuration_service.py # Configuration management services
│ └── stats_service.py # Statistics services (vendor-aware)
├── tasks/ # Background task processing
│ ├── __init__.py
│ ├── task_manager.py # Celery configuration and task management
│ ├── marketplace_import.py # Marketplace CSV import tasks
│ ├── email_tasks.py # Email sending tasks
│ ├── media_processing.py # Image processing and optimization tasks
│ ├── search_indexing.py # Search index maintenance tasks
│ ├── analytics_tasks.py # Analytics and reporting tasks
│ ├── cleanup_tasks.py # Data cleanup and maintenance tasks
│ └── backup_tasks.py # Backup and recovery tasks
├── models/
│ ├── database/ # SQLAlchemy ORM models
│ │ ├── __init__.py # Import all models for easy access
│ │ ├── base.py # Base model class and common mixins (TimestampMixin)
│ │ ├── user.py # User model (with vendor relationships)
│ │ ├── vendor.py # Vendor, VendorUser, Role models
│ │ ├── customer.py # Customer, CustomerAddress models (vendor-scoped)
│ │ ├── marketplace_product.py # MarketplaceProduct model (staging data)
│ │ ├── product.py # Product model (vendor catalog)
│ │ ├── order.py # Order, OrderItem models (vendor-scoped)
│ │ ├── payment.py # Payment, PaymentMethod, VendorPaymentConfig models
│ │ ├── inventory.py # Inventory, InventoryMovement models (catalog products)
│ │ ├── marketplace.py # MarketplaceImportJob model
│ │ ├── media.py # MediaFile, ProductMedia models
│ │ ├── notification.py # NotificationTemplate, NotificationQueue, NotificationLog
│ │ ├── search.py # SearchIndex, SearchQuery models
│ │ ├── audit.py # AuditLog, DataExportLog models
│ │ ├── monitoring.py # PerformanceMetric, ErrorLog, SystemAlert models
│ │ ├── backup.py # BackupLog, RestoreLog models
│ │ ├── configuration.py # PlatformConfig, VendorConfig, FeatureFlag models
│ │ ├── task.py # TaskLog model
│ │ └── admin.py # Admin-specific models
│ │ # - AdminAuditLog: Track all admin actions
│ │ # - AdminNotification: System alerts for admins
│ │ # - AdminSetting: Platform-wide settings
│ │ # - PlatformAlert: System health alerts
│ │ # - AdminSession: Admin login session tracking
│ └── schema/ # Pydantic models for API validation
│ ├── __init__.py # Common imports
│ ├── base.py # Base Pydantic models
│ ├── auth.py # Login, Token, User response models
│ ├── vendor.py # Vendor management models
│ ├── customer.py # Customer request/response models
│ ├── team.py # Team management models
│ ├── marketplace_product.py # Marketplace staging models
│ ├── product.py # Vendor catalog models
│ ├── order.py # Order models (vendor-scoped)
│ ├── payment.py # Payment models
│ ├── inventory.py # Inventory operation models
│ ├── marketplace.py # Marketplace import job models
│ ├── media.py # Media/file management models
│ ├── notification.py # Notification models
│ ├── search.py # Search models
│ ├── monitoring.py # Monitoring models
│ ├── admin.py # Admin operation models
│ │ # - AdminAuditLog schemas (Response, Filters, List)
│ │ # - AdminNotification schemas (Create, Response, Update, List)
│ │ # - AdminSetting schemas (Create, Response, Update, List)
│ │ # - PlatformAlert schemas (Create, Response, Resolve, List)
│ │ # - BulkOperation schemas (BulkVendorAction, BulkUserAction)
│ │ # - AdminDashboardStats, SystemHealthResponse
│ │ # - AdminSession schemas
│ └── stats.py # Statistics response models
├── middleware/
│ ├── auth.py # JWT authentication
│ ├── vendor_context.py # Vendor context detection and injection
│ ├── rate_limiter.py # Rate limiting
│ ├── logging_middleware.py # Request logging
│ └── decorators.py # Cross-cutting concern decorators
├── storage/ # Storage backends
│ ├── __init__.py
│ ├── backends.py # Storage backend implementations
│ └── utils.py # Storage utilities
├── static/ # Frontend assets (No build step!)
│ ├── admin/ # Super admin interface
│ │ ├── login.html # Admin login page
│ │ ├── dashboard.html # Admin dashboard
│ │ ├── vendors.html # Vendor management
│ │ ├── users.html # User management
│ │ ├── marketplace.html # System-wide marketplace monitoring
│ │ ├── audit_logs.html # Audit log viewer (NEW - TO CREATE)
│ │ ├── settings.html # Platform settings (NEW - TO CREATE)
│ │ ├── notifications.html # Admin notifications (NEW - TO CREATE)
│ │ └── monitoring.html # System monitoring
│ ├── vendor/ # Vendor admin interface
│ │ ├── login.html # Vendor team login (TO COMPLETE)
│ │ ├── dashboard.html # Vendor dashboard (TO COMPLETE)
│ │ └── admin/ # Vendor admin pages
│ │ ├── products.html # Catalog management (Product table)
│ │ ├── marketplace/ # Marketplace integration
│ │ │ ├── imports.html # Import jobs & history
│ │ │ ├── browse.html # Browse marketplace products (staging)
│ │ │ ├── selected.html # Selected products (pre-publish)
│ │ │ └── config.html # Marketplace configuration
│ │ ├── orders.html # Order management
│ │ ├── customers.html # Customer management
│ │ ├── teams.html # Team management
│ │ ├── inventory.html # Inventory management (catalog products)
│ │ ├── payments.html # Payment configuration
│ │ ├── media.html # Media library
│ │ ├── notifications.html # Notification templates & logs
│ │ └── settings.html # Vendor settings
│ ├── shop/ # Customer-facing shop interface
│ │ ├── home.html # Shop homepage
│ │ ├── products.html # Product catalog (Product table only)
│ │ ├── product.html # Product detail page
│ │ ├── search.html # Search results page
│ │ ├── cart.html # Shopping cart
│ │ ├── checkout.html # Checkout process
│ │ └── account/ # Customer account pages
│ │ ├── login.html # Customer login
│ │ ├── register.html # Customer registration
│ │ ├── profile.html # Customer profile
│ │ ├── orders.html # Order history
│ │ └── addresses.html # Address management
│ ├── css/
│ │ ├── admin/ # Admin interface styles
│ │ │ └── admin.css
│ │ ├── vendor/ # Vendor interface styles
│ │ │ └── vendor.css
│ │ ├── shop/ # Customer shop styles
│ │ │ └── shop.css
│ │ ├── shared/ # Common styles (base.css, auth.css)
│ │ │ ├── base.css # CSS variables, utility classes
│ │ │ └── auth.css # Login/auth page styles
│ │ └── themes/ # Vendor-specific themes (future)
│ └── js/
│ ├── shared/ # Common JavaScript utilities
│ │ ├── vendor-context.js # Vendor context detection & management
│ │ ├── api-client.js # API communication utilities
│ │ ├── notification.js # Notification handling
│ │ ├── media-upload.js # File upload utilities
│ │ └── utils.js # General utilities
│ ├── admin/ # Admin interface scripts (Alpine.js components)
│ │ ├── dashboard.js # Admin dashboard
│ │ ├── vendors.js # Vendor management
│ │ ├── audit-logs.js # Audit log viewer (NEW - TO CREATE)
│ │ ├── settings.js # Platform settings (NEW - TO CREATE)
│ │ ├── monitoring.js # System monitoring
│ │ └── analytics.js # Admin analytics
│ ├── vendor/ # Vendor interface scripts (Alpine.js components)
│ │ ├── products.js # Catalog management
│ │ ├── marketplace.js # Marketplace integration
│ │ ├── orders.js # Order management
│ │ ├── payments.js # Payment configuration
│ │ ├── media.js # Media management
│ │ └── dashboard.js # Vendor dashboard
│ └── shop/ # Customer shop scripts (Alpine.js components)
│ ├── catalog.js # Product browsing
│ ├── search.js # Product search
│ ├── cart.js # Shopping cart
│ ├── checkout.js # Checkout process
│ └── account.js # Customer account
├── tests/ # Comprehensive test suite
│ ├── unit/ # Unit tests
│ │ ├── services/ # Service layer tests
│ │ │ ├── test_admin_service.py
│ │ │ ├── test_admin_audit_service.py # NEW
│ │ │ ├── test_admin_settings_service.py # NEW
│ │ │ ├── test_marketplace_service.py
│ │ │ ├── test_product_service.py
│ │ │ ├── test_payment_service.py
│ │ │ ├── test_notification_service.py
│ │ │ ├── test_search_service.py
│ │ │ ├── test_media_service.py
│ │ │ └── test_cache_service.py
│ │ ├── models/ # Model tests
│ │ │ ├── test_admin_models.py # NEW
│ │ │ ├── test_marketplace_product.py
│ │ │ ├── test_product.py
│ │ │ ├── test_payment.py
│ │ │ └── test_vendor.py
│ │ └── api/ # API endpoint tests
│ │ ├── test_admin_api.py
│ │ ├── test_admin_audit_api.py # NEW
│ │ ├── test_admin_settings_api.py # NEW
│ │ ├── test_vendor_api.py
│ │ └── test_public_api.py
│ ├── integration/ # Integration tests
│ │ ├── test_marketplace_workflow.py
│ │ ├── test_order_workflow.py
│ │ ├── test_payment_workflow.py
│ │ ├── test_audit_workflow.py # NEW
│ │ └── test_notification_workflow.py
│ ├── e2e/ # End-to-end tests
│ │ ├── test_vendor_onboarding.py
│ │ ├── test_customer_journey.py
│ │ └── test_admin_operations.py
│ └── fixtures/ # Test data fixtures
│ ├── marketplace_data.py # Sample marketplace import data
│ ├── catalog_data.py # Sample vendor catalog data
│ ├── order_data.py # Sample order data
│ └── user_data.py # Sample user and vendor data
├── scripts/ # Utility scripts
│ ├── init_db.py # Database initialization
│ ├── create_admin.py # Create initial admin user
│ ├── init_platform_settings.py # Create default platform settings
│ ├── backup_database.py # Manual backup script
│ ├── seed_data.py # Development data seeding
│ └── migrate_data.py # Data migration utilities
├── docker/ # Docker configuration
│ ├── Dockerfile # Application container
│ ├── docker-compose.yml # Development environment
│ ├── docker-compose.prod.yml # Production environment
│ └── nginx.conf # Nginx configuration
├── docs/ # Documentation
│ ├── slices/ # Vertical slice documentation
│ │ ├── 00_slices_overview.md
│ │ ├── 00_implementation_roadmap.md
│ │ ├── 01_slice1_admin_vendor_foundation.md
│ │ ├── 02_slice2_marketplace_import.md
│ │ ├── 03_slice3_product_catalog.md
│ │ ├── 04_slice4_customer_shopping.md
│ │ └── 05_slice5_order_processing.md
│ ├── api/ # API documentation
│ ├── deployment/ # Deployment guides
│ ├── development/ # Development setup
│ ├── user_guides/ # User manuals
│ ├── 6.complete_naming_convention.md
│ ├── 10.stripe_payment_integration.md
│ ├── 12.project_readme_final.md
│ ├── 13.updated_application_workflows_final.md
│ └── 14.updated_complete_project_structure_final.md
├── .env.example # Environment variables template
├── requirements.txt # Python dependencies
├── requirements-dev.txt # Development dependencies
├── README.md # Project documentation
└── DEPLOYMENT.md # Deployment instructions
```
## Key Changes from Previous Version
### Admin Infrastructure
**Database Models** (`models/database/admin.py`):
-`AdminAuditLog` - Complete audit trail of all admin actions
-`AdminNotification` - System notifications for admins
-`AdminSetting` - Platform-wide configuration with encryption support
-`PlatformAlert` - System health and issue tracking
-`AdminSession` - Admin login session tracking
**Pydantic Schemas** (`models/schema/admin.py`):
- ✅ Comprehensive request/response models for all admin operations
- ✅ Filtering and pagination schemas
- ✅ Bulk operation schemas (vendor/user actions)
- ✅ System health monitoring schemas
**Services**:
-`admin_audit_service.py` - Audit logging functionality
-`admin_settings_service.py` - Platform settings with type conversion
**API Endpoints**:
-`/api/v1/admin/audit/*` - Audit log querying and filtering
-`/api/v1/admin/settings/*` - Settings CRUD operations
-`/api/v1/admin/notifications/*` - Notifications & alerts (structure ready)
### Naming Convention Fixes
- ✅ Changed `models/schemas/` to `models/schema/` (singular)
- ✅ All schema files now consistently use singular naming
### Current Development Status (Slice 1)
**Completed (✅)**:
- Backend database models (User, Vendor, Role, VendorUser, Admin models)
- JWT authentication with bcrypt
- Admin service layer with audit logging capability
- Admin API endpoints (CRUD, dashboard, audit, settings)
- Vendor context middleware
- Admin login page (Alpine.js)
- Admin dashboard (Alpine.js)
- Admin vendor creation page (Alpine.js)
**In Progress (⏳)**:
- Vendor login page (frontend)
- Vendor dashboard page (frontend)
- Admin audit logs page (frontend)
- Admin platform settings page (frontend)
**To Do (📋)**:
- Complete vendor login/dashboard pages
- Integrate audit logging into existing admin operations
- Create admin audit logs frontend
- Create platform settings frontend
- Full testing of Slice 1
- Deployment to staging
## Architecture Principles
### Multi-Tenancy Model
- **Complete Vendor Isolation**: Each vendor operates independently with no data sharing
- **Chinese Wall**: Strict separation between vendor data and operations
- **Self-Service**: Vendors manage their own teams, products, and marketplace integrations
- **Scalable**: Single codebase serves all vendors with vendor-specific customization
### Data Flow Architecture
```
Marketplace CSV → MarketplaceProduct (staging) → Product (catalog) → Order → Analytics
Admin Audit Logs → Platform Settings → Notifications → Monitoring
```
### API Structure
- **Admin APIs** (`/api/v1/admin/`): Platform-level administration
- Vendor management, user management, audit logs, settings, alerts
- **Vendor APIs** (`/api/v1/vendor/`): Vendor-scoped operations (requires vendor context)
- Products, orders, customers, teams, marketplace integration
- **Public APIs** (`/api/v1/public/vendors/{vendor_id}/`): Customer-facing operations
- Shop, products, cart, orders, checkout
- **Shared APIs** (`/api/v1/shared/`): Utility endpoints (health, webhooks)
### Service Layer Architecture
- **Domain Services**: Each service handles one business domain
- **Cross-Cutting Services**: Audit, cache, monitoring, notifications
- **Integration Services**: Payments, search, storage
### Frontend Architecture
- **Zero Build Step**: Alpine.js from CDN, vanilla CSS, no compilation
- **Server-Side Rendering**: Jinja2 templates with Alpine.js enhancement
- **Context-Aware**: Automatic vendor detection via subdomain or path
- **Dynamic Theming**: Vendor-specific customization via CSS variables
- **Role-Based UI**: Permission-driven interface elements
## Key Features
### Core Business Features
- Multi-vendor marketplace with complete isolation
- Marketplace product import and curation workflow
- Comprehensive order and payment processing
- Customer management with vendor-scoped accounts
- Team management with role-based permissions
- Inventory tracking and management
### Advanced Features
- Email notification system with vendor branding
- File and media management with CDN integration
- Advanced search with Elasticsearch
- Multi-layer caching for performance
- Comprehensive audit logging for compliance
- Real-time monitoring and alerting
- Automated backup and disaster recovery
- Configuration management with feature flags
### Security Features
- JWT-based authentication with vendor context
- Role-based access control with granular permissions
- Complete vendor data isolation at all layers
- Audit trails for all operations
- Secure file storage with access controls
- Rate limiting and abuse prevention
### Integration Capabilities
- Stripe Connect for multi-vendor payments
- Marketplace integrations (Letzshop with extensible framework)
- Multiple storage backends (Local, S3, GCS)
- Search engines (Elasticsearch with database fallback)
- Email providers (SendGrid, SMTP)
- Monitoring and alerting systems
## Technology Integration
### Database Layer
- **PostgreSQL**: Primary database with ACID compliance (SQLite for development)
- **Redis**: Caching and session storage
- **Elasticsearch**: Search and analytics (optional)
### Background Processing
- **Celery**: Distributed task processing
- **Redis**: Message broker and result backend
- **Monitoring**: Task progress and error tracking
### External Services
- **Stripe Connect**: Multi-vendor payment processing
- **SendGrid/SMTP**: Email delivery with vendor branding
- **AWS S3/GCS**: Cloud storage for media files
- **CloudFront/CloudFlare**: CDN for static assets
### Monitoring & Compliance
- **Admin Audit Logs**: Complete trail of all admin actions
- **Platform Settings**: Centralized configuration management
- **System Alerts**: Automated health monitoring
- **Error Tracking**: Comprehensive error logging
## Deployment Modes
### Development Mode
- **Path-based routing**: `localhost:8000/vendor/vendorname/`
- **Admin access**: `localhost:8000/admin/`
- **Easy context switching**: Clear vendor separation in URLs
- **Local storage**: Files stored locally with hot reload
### Production Mode
- **Subdomain routing**: `vendorname.platform.com`
- **Admin subdomain**: `admin.platform.com`
- **Custom domains**: `customdomain.com` (enterprise feature)
- **CDN integration**: Optimized asset delivery
- **Distributed caching**: Redis cluster for performance
- **Automated backups**: Scheduled database and file backups
This structure provides a robust foundation for a scalable, multi-tenant ecommerce platform with enterprise-grade features while maintaining clean separation of concerns and supporting multiple deployment modes.

View File

@@ -1,6 +0,0 @@
# Recommended Statistics Service Architecture
## Principle: Single Responsibility
**stats_service** should handle ALL statistics - it's the single source of truth for metrics.
**admin_service** should handle admin operations - user management, vendor management, etc.

View File

@@ -1,943 +0,0 @@
# Authentication System Documentation
**Version:** 1.0
**Last Updated:** November 2024
**Audience:** Development Team
---
## Table of Contents
1. [System Overview](#system-overview)
2. [Architecture](#architecture)
3. [Authentication Contexts](#authentication-contexts)
4. [Implementation Guide](#implementation-guide)
5. [API Reference](#api-reference)
6. [Security Model](#security-model)
7. [Testing Guidelines](#testing-guidelines)
8. [Troubleshooting](#troubleshooting)
---
## System Overview
The LetzShop platform uses a **context-based authentication system** with three isolated security domains:
- **Admin Portal** - Platform administration and management
- **Vendor Portal** - Multi-tenant shop management
- **Customer Shop** - Public storefront and customer accounts
Each context uses **dual authentication** supporting both cookie-based (for HTML pages) and header-based (for API calls) authentication with complete isolation between contexts.
### Key Features
- **Cookie Path Isolation** - Separate cookies per context prevent cross-context access
- **Role-Based Access Control** - Strict enforcement of user roles
- **JWT Token Authentication** - Stateless, secure token-based auth
- **HTTP-Only Cookies** - XSS protection for browser sessions
- **CSRF Protection** - SameSite cookie attribute
- **Comprehensive Logging** - Full audit trail of authentication events
---
## Architecture
### Authentication Flow
```
┌─────────────────────────────────────────────────────┐
│ Client Request │
└─────────────────┬───────────────────────────────────┘
┌───────▼────────┐
│ Route Handler │
└───────┬────────┘
┌───────▼────────────────────────────────┐
│ Authentication Dependency │
│ (from app/api/deps.py) │
└───────┬────────────────────────────────┘
┌─────────────┼─────────────┐
│ │ │
┌───▼───┐ ┌────▼────┐ ┌───▼────┐
│Cookie │ │ Header │ │ None │
└───┬───┘ └────┬────┘ └───┬────┘
│ │ │
└────────┬───┴────────────┘
┌──────▼───────┐
│ Validate JWT │
└──────┬───────┘
┌──────▼──────────┐
│ Check User Role │
└──────┬──────────┘
┌────────┴─────────┐
│ │
┌───▼────┐ ┌─────▼──────┐
│Success │ │ Auth Error │
│Return │ │ 401/403 │
│User │ └────────────┘
└────────┘
```
### Cookie Isolation
Each authentication context uses a separate cookie with path restrictions:
| Context | Cookie Name | Cookie Path | Access Scope |
|----------|------------------|-------------|--------------|
| Admin | `admin_token` | `/admin` | Admin routes only |
| Vendor | `vendor_token` | `/vendor` | Vendor routes only |
| Customer | `customer_token` | `/shop` | Shop routes only |
**Browser Behavior:**
- When requesting `/admin/*`, browser sends `admin_token` cookie only
- When requesting `/vendor/*`, browser sends `vendor_token` cookie only
- When requesting `/shop/*`, browser sends `customer_token` cookie only
This prevents cookie leakage between contexts.
---
## Authentication Contexts
### 1. Admin Context
**Routes:** `/admin/*`
**Role:** `admin`
**Cookie:** `admin_token` (path=/admin)
**Purpose:** Platform administration, vendor management, system configuration.
**Access Control:**
- ✅ Admin users only
- ❌ Vendor users blocked
- ❌ Customer users blocked
**Login Endpoint:**
```
POST /api/v1/admin/auth/login
```
### 2. Vendor Context
**Routes:** `/vendor/*`
**Role:** `vendor`
**Cookie:** `vendor_token` (path=/vendor)
**Purpose:** Vendor shop management, product catalog, orders, team management.
**Access Control:**
- ❌ Admin users blocked (admins use admin portal for vendor management)
- ✅ Vendor users (owners and team members)
- ❌ Customer users blocked
**Login Endpoint:**
```
POST /api/v1/vendor/auth/login
```
### 3. Customer Context
**Routes:** `/shop/account/*` (authenticated), `/shop/*` (public)
**Role:** `customer`
**Cookie:** `customer_token` (path=/shop)
**Purpose:** Product browsing (public), customer accounts, orders, profile management.
**Access Control:**
- **Public Routes** (`/shop/products`, `/shop/cart`, etc.):
- ✅ Anyone can access (no authentication)
- **Account Routes** (`/shop/account/*`):
- ❌ Admin users blocked
- ❌ Vendor users blocked
- ✅ Customer users only
**Login Endpoint:**
```
POST /api/v1/public/vendors/{vendor_id}/customers/login
```
---
## Implementation Guide
### Module Structure
```
app/api/
├── deps.py # Authentication dependencies
├── v1/
├── admin/
│ └── auth.py # Admin authentication endpoints
├── vendor/
│ └── auth.py # Vendor authentication endpoints
└── public/vendors/
└── auth.py # Customer authentication endpoints
```
### For HTML Pages (Server-Rendered)
Use the `*_from_cookie_or_header` functions for pages that users navigate to:
```python
from fastapi import APIRouter, Request, Depends
from fastapi.responses import HTMLResponse
from sqlalchemy.orm import Session
from app.api.deps import (
get_current_admin_from_cookie_or_header,
get_current_vendor_from_cookie_or_header,
get_current_customer_from_cookie_or_header,
get_db
)
from models.database.user import User
router = APIRouter()
# Admin page
@router.get("/admin/dashboard", response_class=HTMLResponse)
async def admin_dashboard(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("admin/dashboard.html", {
"request": request,
"user": current_user
})
# Vendor page
@router.get("/vendor/{vendor_code}/dashboard", response_class=HTMLResponse)
async def vendor_dashboard(
request: Request,
vendor_code: str,
current_user: User = Depends(get_current_vendor_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("vendor/dashboard.html", {
"request": request,
"user": current_user,
"vendor_code": vendor_code
})
# Customer account page
@router.get("/shop/account/dashboard", response_class=HTMLResponse)
async def customer_dashboard(
request: Request,
current_user: User = Depends(get_current_customer_from_cookie_or_header),
db: Session = Depends(get_db)
):
return templates.TemplateResponse("shop/account/dashboard.html", {
"request": request,
"user": current_user
})
```
### For API Endpoints (JSON Responses)
Use the `*_api` functions for API endpoints to enforce header-based authentication:
```python
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import (
get_current_admin_api,
get_current_vendor_api,
get_current_customer_api,
get_db
)
from models.database.user import User
router = APIRouter()
# Admin API
@router.post("/api/v1/admin/vendors")
def create_vendor(
vendor_data: VendorCreate,
current_user: User = Depends(get_current_admin_api),
db: Session = Depends(get_db)
):
# Only accepts Authorization header (no cookies)
# Better security - prevents CSRF attacks
return {"message": "Vendor created"}
# Vendor API
@router.post("/api/v1/vendor/{vendor_code}/products")
def create_product(
vendor_code: str,
product_data: ProductCreate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
):
return {"message": "Product created"}
# Customer API
@router.post("/api/v1/shop/orders")
def create_order(
order_data: OrderCreate,
current_user: User = Depends(get_current_customer_api),
db: Session = Depends(get_db)
):
return {"message": "Order created"}
```
### For Public Routes (No Authentication)
Simply don't use any authentication dependency:
```python
@router.get("/shop/products")
async def public_products(request: Request):
# No authentication required
return templates.TemplateResponse("shop/products.html", {
"request": request
})
```
---
## API Reference
### Authentication Dependencies
All authentication functions are in `app/api/deps.py`:
#### `get_current_admin_from_cookie_or_header()`
**Purpose:** Authenticate admin users for HTML pages
**Accepts:** Cookie (`admin_token`) OR Authorization header
**Returns:** `User` object with `role="admin"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `AdminRequiredException` - User is not admin
**Usage:**
```python
current_user: User = Depends(get_current_admin_from_cookie_or_header)
```
#### `get_current_admin_api()`
**Purpose:** Authenticate admin users for API endpoints
**Accepts:** Authorization header ONLY
**Returns:** `User` object with `role="admin"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `AdminRequiredException` - User is not admin
**Usage:**
```python
current_user: User = Depends(get_current_admin_api)
```
#### `get_current_vendor_from_cookie_or_header()`
**Purpose:** Authenticate vendor users for HTML pages
**Accepts:** Cookie (`vendor_token`) OR Authorization header
**Returns:** `User` object with `role="vendor"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `InsufficientPermissionsException` - User is not vendor or is admin
**Usage:**
```python
current_user: User = Depends(get_current_vendor_from_cookie_or_header)
```
#### `get_current_vendor_api()`
**Purpose:** Authenticate vendor users for API endpoints
**Accepts:** Authorization header ONLY
**Returns:** `User` object with `role="vendor"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `InsufficientPermissionsException` - User is not vendor or is admin
**Usage:**
```python
current_user: User = Depends(get_current_vendor_api)
```
#### `get_current_customer_from_cookie_or_header()`
**Purpose:** Authenticate customer users for HTML pages
**Accepts:** Cookie (`customer_token`) OR Authorization header
**Returns:** `User` object with `role="customer"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `InsufficientPermissionsException` - User is not customer (admin/vendor blocked)
**Usage:**
```python
current_user: User = Depends(get_current_customer_from_cookie_or_header)
```
#### `get_current_customer_api()`
**Purpose:** Authenticate customer users for API endpoints
**Accepts:** Authorization header ONLY
**Returns:** `User` object with `role="customer"`
**Raises:**
- `InvalidTokenException` - No token or invalid token
- `InsufficientPermissionsException` - User is not customer (admin/vendor blocked)
**Usage:**
```python
current_user: User = Depends(get_current_customer_api)
```
#### `get_current_user()`
**Purpose:** Authenticate any user (no role checking)
**Accepts:** Authorization header ONLY
**Returns:** `User` object (any role)
**Raises:**
- `InvalidTokenException` - No token or invalid token
**Usage:**
```python
current_user: User = Depends(get_current_user)
```
### Login Responses
All login endpoints return:
```python
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"role": "admin",
"is_active": true
}
}
```
Additionally, the response sets an HTTP-only cookie:
- Admin: `admin_token` (path=/admin)
- Vendor: `vendor_token` (path=/vendor)
- Customer: `customer_token` (path=/shop)
---
## Security Model
### Role-Based Access Control Matrix
| User Role | Admin Portal | Vendor Portal | Shop Catalog | Customer Account |
|-----------|--------------|---------------|--------------|------------------|
| Admin | ✅ Full | ❌ Blocked | ✅ View | ❌ Blocked |
| Vendor | ❌ Blocked | ✅ Full | ✅ View | ❌ Blocked |
| Customer | ❌ Blocked | ❌ Blocked | ✅ View | ✅ Full |
| Anonymous | ❌ Blocked | ❌ Blocked | ✅ View | ❌ Blocked |
### Cookie Security Settings
All authentication cookies use the following security attributes:
```python
response.set_cookie(
key="<context>_token",
value=jwt_token,
httponly=True, # JavaScript cannot access (XSS protection)
secure=True, # HTTPS only in production
samesite="lax", # CSRF protection
max_age=3600, # Matches JWT expiry
path="/<context>" # Path restriction for isolation
)
```
### Token Validation
JWT tokens include:
- `sub` - User ID
- `role` - User role (admin/vendor/customer)
- `exp` - Expiration timestamp
- `iat` - Issued at timestamp
Tokens are validated on every request:
1. Extract token from cookie or header
2. Verify JWT signature
3. Check expiration
4. Load user from database
5. Verify user is active
6. Check role matches route requirements
### HTTPS Requirement
**Production Environment:**
- All cookies have `secure=True`
- HTTPS required for all authenticated routes
- HTTP requests automatically redirect to HTTPS
**Development Environment:**
- Cookies have `secure=False` for local testing
- HTTP allowed (http://localhost:8000)
---
## Testing Guidelines
### Manual Testing with Browser
#### Test Admin Authentication
1. **Navigate to admin login:**
```
http://localhost:8000/admin/login
```
2. **Login with admin credentials:**
- Username: `admin`
- Password: `admin123` (or your configured admin password)
3. **Verify cookie in DevTools:**
- Open DevTools → Application → Cookies
- Look for `admin_token` cookie
- Verify `Path` is `/admin`
- Verify `HttpOnly` is checked
- Verify `SameSite` is `Lax`
4. **Test navigation:**
- Navigate to `/admin/dashboard` - Should work ✅
- Navigate to `/vendor/TESTVENDOR/dashboard` - Should fail (cookie not sent) ❌
- Navigate to `/shop/account/dashboard` - Should fail (cookie not sent) ❌
5. **Logout:**
```
POST /api/v1/admin/auth/logout
```
#### Test Vendor Authentication
1. **Navigate to vendor login:**
```
http://localhost:8000/vendor/{VENDOR_CODE}/login
```
2. **Login with vendor credentials**
3. **Verify cookie in DevTools:**
- Look for `vendor_token` cookie
- Verify `Path` is `/vendor`
4. **Test navigation:**
- Navigate to `/vendor/{VENDOR_CODE}/dashboard` - Should work ✅
- Navigate to `/admin/dashboard` - Should fail ❌
- Navigate to `/shop/account/dashboard` - Should fail ❌
#### Test Customer Authentication
1. **Navigate to customer login:**
```
http://localhost:8000/shop/account/login
```
2. **Login with customer credentials**
3. **Verify cookie in DevTools:**
- Look for `customer_token` cookie
- Verify `Path` is `/shop`
4. **Test navigation:**
- Navigate to `/shop/account/dashboard` - Should work ✅
- Navigate to `/admin/dashboard` - Should fail ❌
- Navigate to `/vendor/{CODE}/dashboard` - Should fail ❌
### API Testing with curl
#### Test Admin API
```bash
# Login
curl -X POST http://localhost:8000/api/v1/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
# Save the access_token from response
# Test authenticated endpoint
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/vendor/TESTVENDOR/products \
-H "Authorization: Bearer <admin_access_token>"
# Should return 403 Forbidden
```
#### Test Vendor API
```bash
# Login
curl -X POST http://localhost:8000/api/v1/vendor/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"vendor","password":"vendor123"}'
# Test authenticated endpoint
curl http://localhost:8000/api/v1/vendor/TESTVENDOR/products \
-H "Authorization: Bearer <vendor_access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <vendor_access_token>"
# Should return 403 Forbidden
```
#### Test Customer API
```bash
# Login
curl -X POST http://localhost:8000/api/v1/public/vendors/1/customers/login \
-H "Content-Type: application/json" \
-d '{"username":"customer","password":"customer123"}'
# Test authenticated endpoint with token
curl http://localhost:8000/api/v1/shop/orders \
-H "Authorization: Bearer <customer_access_token>"
# Test cross-context blocking
curl http://localhost:8000/api/v1/admin/vendors \
-H "Authorization: Bearer <customer_access_token>"
# Should return 403 Forbidden
```
### Frontend JavaScript Testing
#### Login and Store Token
```javascript
// Admin login
async function loginAdmin(username, password) {
const response = await fetch('/api/v1/admin/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
// Cookie is set automatically
// Optionally store token for API calls
localStorage.setItem('admin_token', data.access_token);
// Redirect to dashboard
window.location.href = '/admin/dashboard';
}
```
#### Make API Call with Token
```javascript
// API call with token
async function fetchVendors() {
const token = localStorage.getItem('admin_token');
const response = await fetch('/api/v1/admin/vendors', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
}
```
#### Page Navigation (Cookie Automatic)
```javascript
// Just navigate - cookie sent automatically
window.location.href = '/admin/dashboard';
// Browser automatically includes admin_token cookie
```
### Automated Testing
#### Test Cookie Isolation
```python
import pytest
from fastapi.testclient import TestClient
def test_admin_cookie_not_sent_to_vendor_routes(client: TestClient):
# Login as admin
response = client.post('/api/v1/admin/auth/login', json={
'username': 'admin',
'password': 'admin123'
})
# Try to access vendor route (cookie should not be sent)
response = client.get('/vendor/TESTVENDOR/dashboard')
# Should redirect to login or return 401
assert response.status_code in [302, 401]
def test_vendor_token_blocked_from_admin_api(client: TestClient):
# Login as vendor
response = client.post('/api/v1/vendor/auth/login', json={
'username': 'vendor',
'password': 'vendor123'
})
vendor_token = response.json()['access_token']
# Try to access admin API with vendor token
response = client.get(
'/api/v1/admin/vendors',
headers={'Authorization': f'Bearer {vendor_token}'}
)
# Should return 403 Forbidden
assert response.status_code == 403
```
---
## Troubleshooting
### Common Issues
#### "Invalid token" error when navigating to pages
**Symptom:** User is logged in but gets "Invalid token" error
**Causes:**
- Token expired (default: 1 hour)
- Cookie was deleted
- Wrong cookie being sent
**Solution:**
- Check cookie expiration in DevTools
- Re-login to get fresh token
- Verify correct cookie exists with correct path
#### Cookie not being sent to endpoints
**Symptom:** API calls work with Authorization header but pages don't load
**Causes:**
- Cookie path mismatch
- Cookie expired
- Wrong domain
**Solution:**
- Verify cookie path matches route (e.g., `/admin` cookie for `/admin/*` routes)
- Check cookie expiration
- Ensure cookie domain matches current domain
#### "Admin cannot access vendor portal" error
**Symptom:** Admin user cannot access vendor routes
**Explanation:** This is intentional security design. Admins have their own portal at `/admin`. To manage vendors, use admin routes:
- View vendors: `/admin/vendors`
- Edit vendor: `/admin/vendors/{code}/edit`
Admins should not log into vendor portal as this violates security boundaries.
#### "Customer cannot access admin/vendor routes" error
**Symptom:** Customer trying to access management interfaces
**Explanation:** Customers only have access to:
- Public shop routes: `/shop/products`, etc.
- Their account: `/shop/account/*`
Admin and vendor portals are not accessible to customers.
#### Token works in Postman but not in browser
**Cause:** Postman uses Authorization header, browser uses cookies
**Solution:**
- For API testing: Use Authorization header
- For browser testing: Rely on cookies (automatic)
- For JavaScript API calls: Add Authorization header manually
### Debugging Tips
#### Check Cookie in Browser
```javascript
// In browser console
document.cookie.split(';').forEach(c => console.log(c.trim()));
```
#### Decode JWT Token
```javascript
// In browser console
function parseJwt(token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
const token = localStorage.getItem('admin_token');
console.log(parseJwt(token));
```
#### Check Server Logs
The authentication system logs all auth events:
```
INFO: Admin login successful: admin
INFO: Request: GET /admin/dashboard from 127.0.0.1
INFO: Response: 200 for GET /admin/dashboard (0.045s)
```
Look for:
- Login attempts
- Token validation errors
- Permission denials
---
## Best Practices
### For Developers
1. **Use the right dependency for the job:**
- HTML pages → `get_current_<context>_from_cookie_or_header`
- API endpoints → `get_current_<context>_api`
2. **Don't mix authentication contexts:**
- Admin users should use admin portal
- Vendor users should use vendor portal
- Customers should use shop
3. **Always check user.is_active:**
```python
if not current_user.is_active:
raise UserNotActiveException()
```
4. **Use type hints:**
```python
def my_route(current_user: User = Depends(get_current_admin_api)):
# IDE will have autocomplete for current_user
```
5. **Handle exceptions properly:**
```python
try:
# Your logic
except InvalidTokenException:
# Handle auth failure
except InsufficientPermissionsException:
# Handle permission denial
```
### For Frontend
1. **Store tokens securely:**
- Tokens in localStorage/sessionStorage are vulnerable to XSS
- Prefer using cookies for page navigation
- Only use localStorage for explicit API calls
2. **Always send Authorization header for API calls:**
```javascript
const token = localStorage.getItem('token');
fetch('/api/v1/admin/vendors', {
headers: { 'Authorization': `Bearer ${token}` }
});
```
3. **Handle 401/403 responses:**
```javascript
if (response.status === 401) {
// Redirect to login
window.location.href = '/admin/login';
}
```
4. **Clear tokens on logout:**
```javascript
localStorage.removeItem('token');
// Logout endpoint will clear cookie
await fetch('/api/v1/admin/auth/logout', { method: 'POST' });
```
### Security Considerations
1. **Never log tokens** - They're sensitive credentials
2. **Use HTTPS in production** - Required for secure cookies
3. **Set appropriate token expiration** - Balance security vs UX
4. **Rotate secrets regularly** - JWT signing keys
5. **Monitor failed auth attempts** - Detect brute force attacks
---
## Configuration
### Environment Variables
```bash
# JWT Configuration
JWT_SECRET_KEY=your-secret-key-here
JWT_ALGORITHM=HS256
JWT_EXPIRATION=3600 # 1 hour in seconds
# Environment
ENVIRONMENT=production # or development
# When ENVIRONMENT=production:
# - Cookies use secure=True (HTTPS only)
# - Debug mode is disabled
# - CORS is stricter
```
### Cookie Expiration
Cookies expire when:
1. JWT token expires (default: 1 hour)
2. User logs out (cookie deleted)
3. Browser session ends (for session cookies)
To change expiration:
```python
# In auth endpoint
response.set_cookie(
max_age=7200 # 2 hours
)
```
---
## Support
For questions or issues:
1. Check this documentation first
2. Review server logs for error messages
3. Test with curl to isolate frontend issues
4. Check browser DevTools for cookie issues
5. Contact the backend team
---
## Changelog
### Version 1.0 (November 2024)
- Initial authentication system implementation
- Three-context isolation (admin, vendor, customer)
- Dual authentication support (cookie + header)
- Complete role-based access control
- Comprehensive logging
---
**End of Documentation**

File diff suppressed because it is too large Load Diff

View File

@@ -1,93 +0,0 @@
Frontend Folder Structure
Generated: 25/10/2025 16:45:08.55
==============================================================================
Folder PATH listing for volume Data2
Volume serial number is 0000007B A008:CC27
E:\FASTAPI-MULTITENANT-ECOMMERCE\STATIC
+---admin
| | marketplace.html
| | monitoring.html
| | users.html
| | vendor-edit.html
| | vendors.html
| |
| +---css
| | tailwind.output.css
| |
| +---img
| | create-account-office-dark.jpeg
| | create-account-office.jpeg
| | forgot-password-office-dark.jpeg
| | forgot-password-office.jpeg
| | login-office-dark.jpeg
| | login-office.jpeg
| |
| +---js
| | analytics.js
| | components.js
| | dashboard.js
| | icons-page.js
| | init-alpine.js
| | login.js
| | monitoring.js
| | testing-hub.js
| | users.js
| | vendor-detail.js
| | vendor-edit.js
| | vendors.js
| |
| \---partials
| base-layout.html
|
+---css
| +---admin
| | admin.css
| |
| +---shared
| | auth.css
| | base.css
| | components.css
| | modals.css
| | responsive-utilities.css
| |
| +---shop
| +---themes
| \---vendor
| vendor.css
|
+---js
| +---shared
| | alpine-components.js
| | media-upload.js
| | modal-system.js
| | modal-templates.js
| | notification.js
| | search.js
| | vendor-context.js
| |
| +---shop
| | account.js
| | cart.js
| | catalog.js
| | checkout.js
| | search.js
| | shop-layout-templates.js
| |
| \---vendor
| dashboard.js
| login.js
| marketplace.js
| media.js
| orders.js
| payments.js
| products.js
| vendor-layout-templates.js
|
+---shared
| \---js
| api-client.js
| icons.js
| utils.js

View File

@@ -1,489 +0,0 @@
# Custom Domain Implementation Guide
## Overview
This guide explains how to implement custom domain support for your multi-tenant e-commerce platform, allowing vendors to use their own domains (e.g., `customdomain1.com`) instead of subdomains (`vendor1.platform.com`).
## Architecture Summary
### Current Routing (What You Have)
```
Development: localhost:8000/vendor/vendor1/ → Vendor 1
Production: vendor1.platform.com → Vendor 1
Admin: admin.platform.com → Admin Panel
```
### New Routing (What You're Adding)
```
Custom Domain: customdomain1.com → Vendor 1
Custom Domain: shop.mybrand.com → Vendor 2
Subdomain: vendor1.platform.com → Vendor 1 (still works!)
Path-based: localhost:8000/vendor/vendor1/ → Vendor 1 (still works!)
```
## How It Works
### 1. Domain Mapping Flow
```
Customer visits customdomain1.com
DNS routes to your server
Middleware reads Host header: "customdomain1.com"
Queries vendor_domains table: WHERE domain = 'customdomain1.com'
Finds VendorDomain record → vendor_id = 1
Loads Vendor 1 data
Sets request.state.vendor = Vendor 1
All queries automatically scoped to Vendor 1
Customer sees Vendor 1's shop
```
### 2. Priority Order
The middleware checks in this order:
1. **Custom Domain** (highest priority)
- Checks if host is NOT platform.com/localhost
- Queries `vendor_domains` table
2. **Subdomain**
- Checks if host matches `*.platform.com`
- Queries `vendors.subdomain` field
3. **Path-based** (lowest priority - dev only)
- Checks if path starts with `/vendor/`
- Queries `vendors.subdomain` field
## Implementation Steps
### Step 1: Add Database Table
Create `vendor_domains` table to store custom domain mappings:
```sql
CREATE TABLE vendor_domains (
id SERIAL PRIMARY KEY,
vendor_id INTEGER NOT NULL REFERENCES vendors(id) ON DELETE CASCADE,
domain VARCHAR(255) NOT NULL UNIQUE,
is_primary BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
is_verified BOOLEAN DEFAULT FALSE,
verification_token VARCHAR(100) UNIQUE,
verified_at TIMESTAMP WITH TIME ZONE,
ssl_status VARCHAR(50) DEFAULT 'pending',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_domain_active ON vendor_domains(domain, is_active);
```
**Files to create:**
- `models/database/vendor_domain.py` - Model definition
- `alembic/versions/XXX_add_vendor_domains.py` - Migration
### Step 2: Update Vendor Model
Add relationship to domains:
```python
# models/database/vendor.py
class Vendor(Base):
# ... existing fields ...
domains = relationship(
"VendorDomain",
back_populates="vendor",
cascade="all, delete-orphan"
)
```
### Step 3: Update Middleware
The middleware needs to check custom domains BEFORE subdomains.
**Key changes in `middleware/vendor_context.py`:**
```python
def detect_vendor_context(request: Request):
host = request.headers.get("host", "")
# NEW: Check if it's a custom domain
if is_custom_domain(host):
return {
"domain": normalize_domain(host),
"detection_method": "custom_domain"
}
# EXISTING: Check subdomain
if is_subdomain(host):
return {
"subdomain": extract_subdomain(host),
"detection_method": "subdomain"
}
# EXISTING: Check path
if is_path_based(request.path):
return {
"subdomain": extract_from_path(request.path),
"detection_method": "path"
}
```
### Step 4: Create Admin Interface
Create API endpoints for managing domains:
```python
POST /api/v1/admin/vendors/{vendor_id}/domains - Add domain
GET /api/v1/admin/vendors/{vendor_id}/domains - List domains
PUT /api/v1/admin/domains/{domain_id} - Update domain
DELETE /api/v1/admin/domains/{domain_id} - Remove domain
POST /api/v1/admin/domains/{domain_id}/verify - Verify ownership
```
**Files to create:**
- `app/api/v1/admin/vendor_domains.py` - API endpoints
- `app/templates/admin/vendor_domains.html` - HTML interface
### Step 5: DNS Verification
To prevent domain hijacking, verify the vendor owns the domain:
**Verification Flow:**
1. Vendor adds domain in admin panel
2. System generates verification token: `abc123xyz`
3. Vendor adds DNS TXT record:
```
Name: _wizamart-verify.customdomain1.com
Type: TXT
Value: abc123xyz
```
4. Admin clicks "Verify" button
5. System queries DNS, checks for token
6. If found, marks domain as verified
**Code:**
```python
@router.post("/domains/{domain_id}/verify")
def verify_domain(domain_id: int, db: Session):
domain = db.query(VendorDomain).get(domain_id)
# Query DNS for TXT record
txt_records = dns.resolver.resolve(
f"_wizamart-verify.{domain.domain}",
'TXT'
)
# Check if token matches
for txt in txt_records:
if txt.to_text() == domain.verification_token:
domain.is_verified = True
db.commit()
return {"message": "Verified!"}
raise HTTPException(400, "Token not found")
```
### Step 6: DNS Configuration
Vendor must configure their domain's DNS:
**Option A: Point to Platform (Simple)**
```
Type: A
Name: @
Value: 123.45.67.89 (your server IP)
Type: A
Name: www
Value: 123.45.67.89
```
**Option B: CNAME to Platform (Better)**
```
Type: CNAME
Name: @
Value: platform.com
Type: CNAME
Name: www
Value: platform.com
```
**Option C: Cloudflare Proxy (Best)**
```
Enable Cloudflare proxy → Automatic SSL + CDN
```
### Step 7: SSL/TLS Certificates
For HTTPS on custom domains, you need SSL certificates.
**Option A: Wildcard Certificate (Simplest for subdomains)**
```bash
# Only works for *.platform.com
certbot certonly --dns-cloudflare \
-d "*.platform.com" \
-d "platform.com"
```
**Option B: Let's Encrypt with Certbot (Per-domain)**
```bash
# For each custom domain
certbot certonly --webroot \
-w /var/www/html \
-d customdomain1.com \
-d www.customdomain1.com
```
**Option C: Cloudflare (Recommended)**
- Vendor uses Cloudflare for their domain
- Cloudflare provides SSL automatically
- No server-side certificate management needed
**Option D: AWS Certificate Manager**
- Use with AWS ALB/CloudFront
- Automatic certificate provisioning
- Free SSL certificates
### Step 8: Web Server Configuration
Configure Nginx/Apache to handle multiple domains:
**Nginx Configuration:**
```nginx
# /etc/nginx/sites-available/platform
server {
listen 80;
listen 443 ssl http2;
# Accept ANY domain
server_name _;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/platform.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/platform.com/privkey.pem;
# Pass Host header to FastAPI
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
**Key Points:**
- `server_name _;` accepts ALL domains
- `proxy_set_header Host $host;` passes domain to FastAPI
- FastAPI middleware reads the Host header to identify vendor
## Testing Guide
### Test 1: Subdomain (Existing)
```bash
# Development
curl -H "Host: vendor1.localhost:8000" http://localhost:8000/
# Production
curl https://vendor1.platform.com/
```
### Test 2: Custom Domain (New)
```bash
# Add to /etc/hosts for testing:
127.0.0.1 customdomain1.com
# Test locally
curl -H "Host: customdomain1.com" http://localhost:8000/
# Production
curl https://customdomain1.com/
```
### Test 3: Path-based (Development)
```bash
curl http://localhost:8000/vendor/vendor1/
```
## Data Flow Example
### Example: Customer visits customdomain1.com
**Step 1: DNS Resolution**
```
customdomain1.com → 123.45.67.89 (your server)
```
**Step 2: HTTP Request**
```
GET / HTTP/1.1
Host: customdomain1.com
```
**Step 3: Nginx Proxy**
```
Nginx receives request
Passes to FastAPI with Host header intact
FastAPI receives: Host = "customdomain1.com"
```
**Step 4: Middleware Processing**
```python
# vendor_context_middleware runs
host = "customdomain1.com"
# Detect it's a custom domain
is_custom = not host.endswith("platform.com") # True
# Query database
vendor_domain = db.query(VendorDomain).filter(
VendorDomain.domain == "customdomain1.com",
VendorDomain.is_active == True,
VendorDomain.is_verified == True
).first()
# vendor_domain.vendor_id = 5
# Load vendor
vendor = db.query(Vendor).get(5)
# Set in request state
request.state.vendor = vendor # Vendor 5
```
**Step 5: Route Handler**
```python
@router.get("/")
def shop_home(request: Request):
vendor = request.state.vendor # Vendor 5
# All queries automatically filtered to Vendor 5
products = db.query(Product).filter(
Product.vendor_id == vendor.id
).all()
return templates.TemplateResponse("shop/home.html", {
"request": request,
"vendor": vendor,
"products": products
})
```
**Step 6: Response**
```
Customer sees Vendor 5's shop at customdomain1.com
```
## Security Considerations
### 1. Domain Verification
- ALWAYS verify domain ownership via DNS TXT records
- Never allow unverified domains to go live
- Prevents domain hijacking
### 2. SSL/TLS
- Require HTTPS for custom domains
- Validate SSL certificates
- Monitor certificate expiration
### 3. Vendor Isolation
- Double-check vendor_id in all queries
- Never trust user input for vendor selection
- Always use `request.state.vendor`
### 4. Rate Limiting
- Limit domain additions per vendor
- Prevent DNS verification spam
- Monitor failed verification attempts
### 5. Reserved Domains
- Block platform.com and subdomains
- Block reserved names (admin, api, www, mail)
- Validate domain format
## Common Issues & Solutions
### Issue 1: "Domain not found"
**Cause:** DNS not pointing to your server
**Solution:** Check DNS A/CNAME records
### Issue 2: SSL certificate error
**Cause:** No certificate for custom domain
**Solution:** Use Cloudflare or provision certificate
### Issue 3: Vendor not detected
**Cause:** Domain not in vendor_domains table
**Solution:** Add domain via admin panel and verify
### Issue 4: Wrong vendor loaded
**Cause:** Multiple vendors with same domain
**Solution:** Enforce unique constraint on domain column
### Issue 5: Verification fails
**Cause:** DNS TXT record not found
**Solution:** Wait for DNS propagation (5-15 minutes)
## Deployment Checklist
- [ ] Database migration applied
- [ ] Vendor model updated with domains relationship
- [ ] Middleware updated to check custom domains
- [ ] Admin API endpoints created
- [ ] Admin UI created for domain management
- [ ] DNS verification implemented
- [ ] Web server configured to accept all domains
- [ ] SSL certificate strategy decided
- [ ] Testing completed (subdomain + custom domain)
- [ ] Documentation updated
- [ ] Monitoring configured
- [ ] Rate limiting added
## Future Enhancements
### 1. Automatic SSL Provisioning
Use certbot or ACME protocol to automatically provision SSL certificates when vendor adds domain.
### 2. Domain Status Dashboard
Show vendors their domain status:
- DNS configuration status
- SSL certificate status
- Traffic analytics per domain
### 3. Multiple Domains per Vendor
Allow vendors to have multiple custom domains pointing to same shop.
### 4. Domain Transfer
Allow transferring domain from one vendor to another.
### 5. Subdomain Customization
Let vendors choose their subdomain: `mybrand.platform.com`
## Summary
**What you're adding:**
1. Database table to store domain → vendor mappings
2. Middleware logic to detect custom domains
3. Admin interface to manage domains
4. DNS verification system
5. Web server configuration for multi-domain
**What stays the same:**
- Existing subdomain routing still works
- Path-based routing still works for development
- Vendor isolation and security
- All existing functionality
**Result:**
Vendors can use their own domains while you maintain a single codebase with multi-tenant architecture!

View File

@@ -1,400 +0,0 @@
# Custom Domain Support - Executive Summary
## What You Asked For
> "I want to deploy multiple shops on multiple different domains like domain1.com/shop1, domain2.com/shop2"
## What You Currently Have
Your FastAPI multi-tenant e-commerce platform currently supports:
1. **Subdomain routing** (production): `vendor1.platform.com` → Vendor 1
2. **Path-based routing** (development): `localhost:8000/vendor/vendor1/` → Vendor 1
## What's Missing
You **cannot** currently route custom domains like:
- `customdomain1.com` → Vendor 1
- `shop.mybrand.com` → Vendor 2
## The Solution
Add a **domain mapping table** that links custom domains to vendors:
```
customdomain1.com → Vendor 1
customdomain2.com → Vendor 2
```
Your middleware checks this table BEFORE checking subdomains.
## High-Level Architecture
### Current Flow
```
Customer → vendor1.platform.com → Middleware checks subdomain → Finds Vendor 1
```
### New Flow
```
Customer → customdomain1.com → Middleware checks domain mapping → Finds Vendor 1
```
### Priority Order (after implementation)
1. **Custom domain** (check `vendor_domains` table)
2. **Subdomain** (check `vendors.subdomain` - still works!)
3. **Path-based** (development mode - still works!)
## What Changes
### ✅ Changes Required
1. **Database**: Add `vendor_domains` table
2. **Model**: Create `VendorDomain` model
3. **Middleware**: Update to check custom domains first
4. **Config**: Add `platform_domain` setting
### ❌ What Stays the Same
- Existing subdomain routing (still works!)
- Path-based development routing (still works!)
- Vendor isolation and security
- All existing functionality
- API endpoints
- Admin panel
## Implementation Overview
### Database Schema
```sql
vendor_domains
id (PK)
vendor_id (FK vendors.id)
domain (UNIQUE) - e.g., "customdomain1.com"
is_active
is_verified (prevents domain hijacking)
verification_token (for DNS verification)
```
### Middleware Logic
```python
def detect_vendor(request):
host = request.host
# NEW: Check if custom domain
if not host.endswith("platform.com"):
vendor = find_by_custom_domain(host)
if vendor:
return vendor
# EXISTING: Check subdomain
if is_subdomain(host):
vendor = find_by_subdomain(host)
return vendor
# EXISTING: Check path
if is_path_based(request.path):
vendor = find_by_path(request.path)
return vendor
```
## Real-World Example
### Vendor Setup Process
**Step 1: Admin adds domain**
- Admin logs in
- Navigates to Vendor → Domains
- Adds "customdomain1.com"
- Gets verification token: `abc123xyz`
**Step 2: Vendor configures DNS**
At their domain registrar (GoDaddy/Namecheap):
```
Type: A
Name: @
Value: 123.45.67.89 (your server IP)
Type: TXT
Name: _wizamart-verify
Value: abc123xyz
```
**Step 3: Verification**
- Wait 5-15 minutes for DNS propagation
- Admin clicks "Verify Domain"
- System checks DNS for TXT record
- Domain marked as verified ✓
**Step 4: Go Live**
- Customer visits `customdomain1.com`
- Sees Vendor 1's shop
- Everything just works!
## Technical Requirements
### Server Side
- **Nginx**: Accept all domains (`server_name _;`)
- **FastAPI**: Updated middleware
- **Database**: New table for domain mappings
### DNS Side
- Vendor points domain to your server
- A record: `@` → Your server IP
- Verification: TXT record for ownership proof
### SSL/TLS
Three options:
1. **Cloudflare** (easiest - automatic SSL)
2. **Let's Encrypt** (per-domain certificates)
3. **Wildcard** (subdomains only)
## Security
### Domain Verification
✅ Prevents domain hijacking
✅ Requires DNS TXT record
✅ Token-based verification
✅ Vendor must prove ownership
### Vendor Isolation
✅ All queries filtered by vendor_id
✅ No cross-vendor data leakage
✅ Middleware sets request.state.vendor
✅ Enforced at database level
## Benefits
### For Platform Owner (You)
- ✅ More professional offering
- ✅ Enterprise feature for premium vendors
- ✅ Competitive advantage
- ✅ Higher vendor retention
- ✅ Still maintain single codebase
### For Vendors
- ✅ Use their own brand domain
- ✅ Better SEO (own domain)
- ✅ Professional appearance
- ✅ Customer trust
- ✅ Marketing benefits
### For Customers
- ✅ Seamless experience
- ✅ Trust familiar domain
- ✅ No platform branding visible
- ✅ Better user experience
## Implementation Effort
### Minimal Changes
- **New table**: 1 table (`vendor_domains`)
- **New model**: 1 file (`vendor_domain.py`)
- **Updated middleware**: Modify existing file
- **Config**: Add 1 setting
### Time Estimate
- **Core functionality**: 4-6 hours
- **Testing**: 2-3 hours
- **Production deployment**: 2-4 hours
- **Total**: 1-2 days
### Risk Level
- **Low risk**: New feature, doesn't break existing
- **Backward compatible**: Old methods still work
- **Rollback plan**: Simple database rollback
## Testing Strategy
### Development Testing
```bash
# Add to /etc/hosts
127.0.0.1 testdomain.local
# Add test data
INSERT INTO vendor_domains (vendor_id, domain, is_verified)
VALUES (1, 'testdomain.local', true);
# Test
curl http://testdomain.local:8000/
```
### Production Testing
1. Add test domain for one vendor
2. Configure DNS
3. Verify detection works
4. Monitor logs
5. Roll out to more vendors
## Deployment Checklist
### Phase 1: Database
- [ ] Create migration
- [ ] Apply to development
- [ ] Test data insertion
- [ ] Apply to production
### Phase 2: Code
- [ ] Create model
- [ ] Update middleware
- [ ] Update config
- [ ] Deploy to development
- [ ] Test thoroughly
- [ ] Deploy to production
### Phase 3: Infrastructure
- [ ] Update Nginx config
- [ ] Test domain acceptance
- [ ] Configure SSL strategy
- [ ] Set up monitoring
### Phase 4: Launch
- [ ] Document vendor process
- [ ] Train admin team
- [ ] Test with pilot vendor
- [ ] Roll out gradually
## Monitoring & Maintenance
### Key Metrics
- Number of active custom domains
- Domain verification success rate
- Traffic per domain
- SSL certificate status
- Failed domain lookups
### Regular Tasks
- Review unverified domains (weekly)
- Check SSL certificates (monthly)
- Clean up inactive domains (quarterly)
- Monitor DNS changes (automated)
## Alternatives Considered
### ❌ Separate Deployments Per Vendor
- **Pros**: Complete isolation
- **Cons**: High maintenance, expensive, difficult to update
- **Verdict**: Not scalable
### ❌ Nginx-Only Routing
- **Pros**: No application changes
- **Cons**: Hard to manage, no database tracking, no verification
- **Verdict**: Not maintainable
### ✅ Database-Driven Domain Mapping (Recommended)
- **Pros**: Scalable, maintainable, secure, trackable
- **Cons**: Requires implementation effort
- **Verdict**: Best solution
## Success Criteria
- [ ] Can add custom domain via admin panel
- [ ] DNS verification works
- [ ] Custom domain routes to correct vendor
- [ ] Subdomain routing still works
- [ ] Path-based routing still works
- [ ] No cross-vendor data leakage
- [ ] SSL works on custom domains
- [ ] Monitoring in place
- [ ] Documentation complete
## ROI Analysis
### Costs
- Development: 1-2 days (one-time)
- Testing: 0.5 day (one-time)
- Maintenance: 1-2 hours/month
### Benefits
- Premium feature for enterprise vendors
- Higher vendor retention
- Competitive advantage
- Professional appearance
- Better vendor acquisition
### Break-Even
If even 5 vendors are willing to pay $50/month extra for custom domain feature:
- Revenue: $250/month = $3,000/year
- Cost: 2 days development ≈ $2,000
- **Break-even in ~8 months**
## Next Steps
### Immediate (This Week)
1. Review implementation guides
2. Test locally with demo domain
3. Verify approach with team
### Short-Term (This Month)
1. Implement database changes
2. Update middleware
3. Deploy to staging
4. Test with pilot vendor
### Long-Term (This Quarter)
1. Add admin UI
2. Document vendor process
3. Roll out to all vendors
4. Market as premium feature
## Resources Provided
### Documentation
1. **QUICK_START.md** - Get started in 30 minutes
2. **CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md** - Complete guide
3. **ARCHITECTURE_DIAGRAMS.md** - Visual architecture
4. **IMPLEMENTATION_CHECKLIST.md** - Step-by-step tasks
### Code Files
1. **vendor_domain_model.py** - Database model
2. **updated_vendor_context.py** - Updated middleware
3. **vendor_domains_api.py** - Admin API endpoints
4. **migration_vendor_domains.py** - Database migration
5. **config_updates.py** - Configuration changes
## Questions & Answers
**Q: Will this break existing functionality?**
A: No! Subdomain and path-based routing still work. This adds a new layer on top.
**Q: What about SSL certificates?**
A: Recommend Cloudflare (automatic) or Let's Encrypt (per-domain). See full guide.
**Q: How do vendors point their domain?**
A: They add an A record pointing to your server IP. Simple DNS configuration.
**Q: What prevents domain hijacking?**
A: DNS verification via TXT record. Vendor must prove ownership before domain goes live.
**Q: Can one vendor have multiple domains?**
A: Yes! The `vendor_domains` table supports multiple domains per vendor.
**Q: What if vendor removes domain later?**
A: Just mark as `is_active = false` in database. Easy to deactivate.
**Q: Do I need separate servers per domain?**
A: No! Single server accepts all domains. Middleware routes to correct vendor.
## Conclusion
**Is your current architecture capable?**
✅ Yes! Your multi-tenant architecture is perfect for this.
**What needs to change?**
✅ Minimal changes: 1 table, 1 model, middleware update
**Is it worth it?**
✅ Yes! Enterprise feature, competitive advantage, premium pricing
**Risk level?**
✅ Low! Backward compatible, rollback-friendly
**Implementation complexity?**
✅ Medium! 1-2 days for experienced FastAPI developer
**Recommendation:**
**GO FOR IT!** This is a valuable feature that fits naturally into your architecture.
---
**Ready to start?** Begin with `QUICK_START.md` for a 30-minute implementation!

View File

@@ -1,466 +0,0 @@
# Custom Domain Implementation Checklist
## Phase 1: Database Setup
### Step 1.1: Create VendorDomain Model
- [ ] Create file: `models/database/vendor_domain.py`
- [ ] Copy model code from `vendor_domain_model.py`
- [ ] Import in `models/database/__init__.py`
### Step 1.2: Update Vendor Model
- [ ] Open `models/database/vendor.py`
- [ ] Add `domains` relationship
- [ ] Add `primary_domain` and `all_domains` properties
### Step 1.3: Create Migration
- [ ] Generate migration: `alembic revision -m "add vendor domains"`
- [ ] Copy upgrade/downgrade code from `migration_vendor_domains.py`
- [ ] Apply migration: `alembic upgrade head`
- [ ] Verify table exists: `psql -c "\d vendor_domains"`
## Phase 2: Configuration
### Step 2.1: Update Settings
- [ ] Open `app/core/config.py`
- [ ] Add `platform_domain = "platform.com"` (change to your actual domain)
- [ ] Add custom domain settings from `config_updates.py`
- [ ] Update `.env` file with new settings
### Step 2.2: Test Settings
```bash
# In Python shell
from app.core.config import settings
print(settings.platform_domain) # Should print your domain
```
## Phase 3: Middleware Update
### Step 3.1: Backup Current Middleware
```bash
cp middleware/vendor_context.py middleware/vendor_context.py.backup
```
### Step 3.2: Update Middleware
- [ ] Open `middleware/vendor_context.py`
- [ ] Replace with code from `updated_vendor_context.py`
- [ ] Review the three detection methods (custom domain, subdomain, path)
- [ ] Check imports are correct
### Step 3.3: Test Middleware Detection
Create test file `tests/test_vendor_context.py`:
```python
def test_custom_domain_detection():
# Mock request with custom domain
request = MockRequest(host="customdomain1.com")
context = VendorContextManager.detect_vendor_context(request)
assert context["detection_method"] == "custom_domain"
assert context["domain"] == "customdomain1.com"
def test_subdomain_detection():
request = MockRequest(host="vendor1.platform.com")
context = VendorContextManager.detect_vendor_context(request)
assert context["detection_method"] == "subdomain"
assert context["subdomain"] == "vendor1"
def test_path_detection():
request = MockRequest(host="localhost", path="/vendor/vendor1/")
context = VendorContextManager.detect_vendor_context(request)
assert context["detection_method"] == "path"
assert context["subdomain"] == "vendor1"
```
Run tests:
```bash
pytest tests/test_vendor_context.py -v
```
## Phase 4: Admin API Endpoints
### Step 4.1: Create Vendor Domains Router
- [ ] Create file: `app/api/v1/admin/vendor_domains.py`
- [ ] Copy code from `vendor_domains_api.py`
- [ ] Verify imports work
### Step 4.2: Register Router
Edit `app/api/v1/admin/__init__.py`:
```python
from .vendor_domains import router as vendor_domains_router
# In your admin router setup:
admin_router.include_router(
vendor_domains_router,
prefix="/vendors",
tags=["vendor-domains"]
)
```
### Step 4.3: Test API Endpoints
```bash
# Start server
uvicorn main:app --reload
# Test endpoints (use Postman or curl)
# 1. List vendor domains
curl -H "Authorization: Bearer {admin_token}" \
http://localhost:8000/api/v1/admin/vendors/1/domains
# 2. Add domain
curl -X POST \
-H "Authorization: Bearer {admin_token}" \
-H "Content-Type: application/json" \
-d '{"vendor_id": 1, "domain": "test.com"}' \
http://localhost:8000/api/v1/admin/vendors/1/domains
```
## Phase 5: DNS Verification (Optional but Recommended)
### Step 5.1: Install DNS Library
```bash
pip install dnspython
```
### Step 5.2: Test DNS Verification
```python
# In Python shell
import dns.resolver
# Test querying TXT record
answers = dns.resolver.resolve("_wizamart-verify.example.com", "TXT")
for txt in answers:
print(txt.to_text())
```
## Phase 6: Local Testing
### Step 6.1: Test with /etc/hosts
Edit `/etc/hosts`:
```
127.0.0.1 testdomain1.local
127.0.0.1 testdomain2.local
```
### Step 6.2: Add Test Data
```sql
-- Add test vendor
INSERT INTO vendors (subdomain, name, is_active)
VALUES ('testvendor', 'Test Vendor', true);
-- Add test domain
INSERT INTO vendor_domains (vendor_id, domain, is_verified, is_active)
VALUES (1, 'testdomain1.local', true, true);
```
### Step 6.3: Test in Browser
```bash
# Start server
uvicorn main:app --reload
# Visit in browser:
# http://testdomain1.local:8000/
# Check logs for:
# "✓ Vendor found via custom domain: testdomain1.local → Test Vendor"
```
## Phase 7: Web Server Configuration
### Step 7.1: Update Nginx Configuration
Edit `/etc/nginx/sites-available/your-site`:
```nginx
server {
listen 80;
listen 443 ssl http2;
# Accept ALL domains
server_name _;
# SSL configuration (update paths)
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host; # CRITICAL: Pass domain to FastAPI
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
### Step 7.2: Test Nginx Config
```bash
sudo nginx -t
sudo systemctl reload nginx
```
## Phase 8: Production Deployment
### Step 8.1: Pre-deployment Checklist
- [ ] All tests passing
- [ ] Database migration applied
- [ ] Configuration updated in production .env
- [ ] Nginx configured to accept all domains
- [ ] SSL certificate strategy decided
- [ ] Monitoring configured
- [ ] Rollback plan ready
### Step 8.2: Deploy
```bash
# 1. Pull latest code
git pull origin main
# 2. Install dependencies
pip install -r requirements.txt
# 3. Run migrations
alembic upgrade head
# 4. Restart application
sudo systemctl restart your-app
# 5. Restart nginx
sudo systemctl reload nginx
```
### Step 8.3: Verify Deployment
```bash
# Test health endpoint
curl https://platform.com/health
# Check logs
tail -f /var/log/your-app/app.log
# Look for:
# "✓ Vendor found via custom domain: ..."
```
## Phase 9: Vendor Setup Process
### Step 9.1: Admin Adds Domain for Vendor
1. Log into admin panel
2. Go to Vendors → Select Vendor → Domains
3. Click "Add Domain"
4. Enter domain: `customdomain1.com`
5. Click Save
6. Copy verification instructions
### Step 9.2: Vendor Configures DNS
Vendor goes to their domain registrar and adds:
**A Record:**
```
Type: A
Name: @
Value: [your server IP]
TTL: 3600
```
**Verification TXT Record:**
```
Type: TXT
Name: _wizamart-verify
Value: [token from step 9.1]
TTL: 3600
```
### Step 9.3: Admin Verifies Domain
1. Wait 5-15 minutes for DNS propagation
2. In admin panel → Click "Verify Domain"
3. System checks DNS for TXT record
4. If found → Domain marked as verified
5. Domain is now active!
### Step 9.4: Test Custom Domain
```bash
# Visit vendor's custom domain
curl https://customdomain1.com/
# Should show vendor's shop
# Check server logs for confirmation
```
## Phase 10: SSL/TLS Setup
### Option A: Let's Encrypt (Per-Domain)
```bash
# For each custom domain
sudo certbot certonly --webroot \
-w /var/www/html \
-d customdomain1.com \
-d www.customdomain1.com
# Auto-renewal
sudo certbot renew --dry-run
```
### Option B: Cloudflare (Recommended)
1. Vendor adds domain to Cloudflare
2. Cloudflare provides SSL automatically
3. Points Cloudflare DNS to your server
4. No server-side certificate needed
### Option C: Wildcard (Subdomains Only)
```bash
# Only for *.platform.com
sudo certbot certonly --dns-cloudflare \
-d "*.platform.com" \
-d "platform.com"
```
## Troubleshooting Guide
### Issue: Vendor not detected
**Check:**
```sql
-- Is domain in database?
SELECT * FROM vendor_domains WHERE domain = 'customdomain1.com';
-- Is domain verified?
SELECT * FROM vendor_domains
WHERE domain = 'customdomain1.com'
AND is_verified = true
AND is_active = true;
-- Is vendor active?
SELECT v.* FROM vendors v
JOIN vendor_domains vd ON v.id = vd.vendor_id
WHERE vd.domain = 'customdomain1.com';
```
**Check logs:**
```bash
# Look for middleware debug logs
grep "Vendor context" /var/log/your-app/app.log
# Should see:
# "🏪 Vendor context: Shop Name (subdomain) via custom_domain"
```
### Issue: Wrong vendor loaded
**Check for duplicates:**
```sql
-- Should be no duplicates
SELECT domain, COUNT(*)
FROM vendor_domains
GROUP BY domain
HAVING COUNT(*) > 1;
```
### Issue: DNS verification fails
**Check DNS propagation:**
```bash
# Check if TXT record exists
dig _wizamart-verify.customdomain1.com TXT
# Should show verification token
```
### Issue: SSL certificate error
**Options:**
1. Use Cloudflare (easiest)
2. Get Let's Encrypt certificate for domain
3. Tell vendor to use their own SSL proxy
## Monitoring & Maintenance
### Add Logging
```python
# In middleware
logger.info(
f"Request received for {host}",
extra={
"host": host,
"vendor_id": vendor.id if vendor else None,
"detection_method": context.get("detection_method")
}
)
```
### Monitor Metrics
- [ ] Number of active custom domains
- [ ] Failed domain verifications
- [ ] Traffic per domain
- [ ] SSL certificate expirations
### Regular Maintenance
- [ ] Review unverified domains (> 7 days old)
- [ ] Check SSL certificate status
- [ ] Monitor DNS changes
- [ ] Clean up inactive domains
## Success Criteria
- [ ] Existing subdomain routing still works
- [ ] New custom domains can be added via admin
- [ ] DNS verification works
- [ ] Multiple domains can point to same vendor
- [ ] Middleware correctly identifies vendor
- [ ] All vendor queries properly scoped
- [ ] No security vulnerabilities (domain hijacking prevented)
- [ ] Monitoring in place
- [ ] Documentation updated
## Rollback Plan
If something goes wrong:
1. **Database:**
```bash
alembic downgrade -1 # Rollback migration
```
2. **Code:**
```bash
git checkout HEAD~1 # Revert to previous commit
sudo systemctl restart your-app
```
3. **Middleware:**
```bash
cp middleware/vendor_context.py.backup middleware/vendor_context.py
sudo systemctl restart your-app
```
4. **Nginx:**
```bash
# Restore previous nginx config from backup
sudo cp /etc/nginx/sites-available/your-site.backup /etc/nginx/sites-available/your-site
sudo systemctl reload nginx
```
## Next Steps After Implementation
1. **Create Admin UI**
- HTML page for domain management
- Show verification status
- DNS configuration help
2. **Vendor Dashboard**
- Let vendors see their domains
- Domain analytics
- SSL status
3. **Automation**
- Auto-verify domains via webhook
- Auto-provision SSL certificates
- Auto-renewal monitoring
4. **Documentation**
- Vendor help docs
- Admin guide
- API documentation
5. **Testing**
- Load testing with multiple domains
- Security audit
- Penetration testing
---
**Estimated Implementation Time:**
- Phase 1-4: 4-6 hours (core functionality)
- Phase 5-7: 2-3 hours (testing and deployment)
- Phase 8-10: 2-4 hours (production setup and SSL)
**Total: 1-2 days for full implementation**

View File

@@ -1,460 +0,0 @@
# Custom Domain Support - Complete Documentation Package
## 📋 Table of Contents
This package contains everything you need to add custom domain support to your FastAPI multi-tenant e-commerce platform.
## 🚀 Where to Start
### If you want to understand the concept first:
👉 **Start here:** [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md)
- High-level overview
- What changes and what stays the same
- Benefits and ROI analysis
- Decision-making guidance
### If you want to implement quickly:
👉 **Start here:** [QUICK_START.md](QUICK_START.md)
- Get working in 30 minutes
- Minimal steps
- Local testing included
- Perfect for proof-of-concept
### If you want complete understanding:
👉 **Start here:** [CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md](CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md)
- Comprehensive guide
- All technical details
- Security considerations
- Production deployment
## 📚 Documentation Files
### 1. EXECUTIVE_SUMMARY.md
**What it covers:**
- Problem statement and solution
- High-level architecture
- Benefits analysis
- Risk assessment
- ROI calculation
- Q&A section
**Read this if:**
- You need to explain the feature to stakeholders
- You want to understand if it's worth implementing
- You need a business case
- You want to see the big picture
**Reading time:** 10 minutes
---
### 2. QUICK_START.md
**What it covers:**
- 5-step implementation (30 minutes)
- Database setup
- Model creation
- Middleware update
- Local testing
- Troubleshooting
**Read this if:**
- You want to test the concept quickly
- You need a working demo
- You prefer hands-on learning
- You want to validate the approach
**Implementation time:** 30 minutes
---
### 3. CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md
**What it covers:**
- Complete architecture explanation
- Step-by-step implementation
- DNS configuration
- SSL/TLS setup
- Security best practices
- Testing strategies
- Common issues and solutions
- Future enhancements
**Read this if:**
- You're doing production implementation
- You need comprehensive documentation
- You want to understand every detail
- You need reference material
**Reading time:** 45 minutes
---
### 4. ARCHITECTURE_DIAGRAMS.md
**What it covers:**
- Visual architecture diagrams
- Request flow illustrations
- Database relationship diagrams
- Before/after comparisons
- DNS configuration examples
- Decision tree diagrams
**Read this if:**
- You're a visual learner
- You need to explain to others
- You want to see data flow
- You prefer diagrams to text
**Reading time:** 20 minutes
---
### 5. IMPLEMENTATION_CHECKLIST.md
**What it covers:**
- Phase-by-phase implementation plan
- Detailed task checklist
- Testing procedures
- Deployment steps
- Troubleshooting guide
- Rollback plan
- Monitoring setup
**Read this if:**
- You're ready to implement in production
- You need a project plan
- You want to track progress
- You need a deployment guide
**Implementation time:** 1-2 days
---
## 💻 Code Files
All generated code files are in `/home/claude/`:
### Core Implementation Files
**1. vendor_domain_model.py**
- Complete `VendorDomain` SQLAlchemy model
- Domain normalization logic
- Relationships and constraints
- Ready to use in your project
**2. updated_vendor_context.py**
- Enhanced middleware with custom domain support
- Three detection methods (custom domain, subdomain, path)
- Vendor lookup logic
- Drop-in replacement for your current middleware
**3. vendor_domains_api.py**
- Admin API endpoints for domain management
- CRUD operations for domains
- DNS verification endpoint
- Verification instructions endpoint
**4. migration_vendor_domains.py**
- Alembic migration script
- Creates vendor_domains table
- All indexes and constraints
- Upgrade and downgrade functions
**5. config_updates.py**
- Configuration additions for Settings class
- Platform domain setting
- Custom domain features toggle
- DNS verification settings
**6. vendor_model_update.py**
- Updates to Vendor model
- Domains relationship
- Helper properties for domain access
---
## 🎯 Quick Navigation Guide
### I want to...
**...understand what custom domains are**
→ Read: EXECUTIVE_SUMMARY.md (Section: "What You Asked For")
**...see if my architecture can support this**
→ Read: EXECUTIVE_SUMMARY.md (Section: "Is your current architecture capable?")
→ Answer: ✅ YES! You have exactly what you need.
**...test it locally in 30 minutes**
→ Follow: QUICK_START.md
**...understand the technical architecture**
→ Read: ARCHITECTURE_DIAGRAMS.md
→ Read: CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md (Section: "How It Works")
**...implement in production**
→ Follow: IMPLEMENTATION_CHECKLIST.md
→ Reference: CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md
**...see the database schema**
→ Check: ARCHITECTURE_DIAGRAMS.md (Section: "Database Relationships")
→ Use: migration_vendor_domains.py
**...update my middleware**
→ Use: updated_vendor_context.py
→ Reference: CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md (Section: "Middleware Update")
**...add admin endpoints**
→ Use: vendor_domains_api.py
→ Reference: CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md (Section: "Create Admin Endpoints")
**...configure DNS**
→ Read: CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md (Section: "DNS Configuration")
→ Check: ARCHITECTURE_DIAGRAMS.md (Section: "DNS Configuration Examples")
**...set up SSL/TLS**
→ Read: CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md (Section: "SSL/TLS Certificates")
→ Check: IMPLEMENTATION_CHECKLIST.md (Phase 10)
**...verify a domain**
→ Read: CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md (Section: "DNS Verification")
→ Use: vendor_domains_api.py (verify_domain endpoint)
**...troubleshoot issues**
→ Check: QUICK_START.md (Section: "Common Issues & Fixes")
→ Check: IMPLEMENTATION_CHECKLIST.md (Section: "Troubleshooting Guide")
**...roll back if something goes wrong**
→ Follow: IMPLEMENTATION_CHECKLIST.md (Section: "Rollback Plan")
---
## 🔄 Recommended Reading Order
### For First-Time Readers:
1. **EXECUTIVE_SUMMARY.md** - Understand the concept (10 min)
2. **ARCHITECTURE_DIAGRAMS.md** - See visual flow (20 min)
3. **QUICK_START.md** - Test locally (30 min)
4. **CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md** - Deep dive (45 min)
5. **IMPLEMENTATION_CHECKLIST.md** - Plan deployment (review time)
### For Hands-On Implementers:
1. **QUICK_START.md** - Get started immediately
2. **ARCHITECTURE_DIAGRAMS.md** - Understand the flow
3. **CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md** - Reference as needed
4. **IMPLEMENTATION_CHECKLIST.md** - Follow for production
### For Decision Makers:
1. **EXECUTIVE_SUMMARY.md** - Complete overview
2. **ARCHITECTURE_DIAGRAMS.md** - Visual understanding
3. **IMPLEMENTATION_CHECKLIST.md** - See effort required
---
## ✅ What You'll Have After Implementation
### Technical Capabilities:
- ✅ Custom domains route to correct vendors
- ✅ Subdomain routing still works (backward compatible)
- ✅ Path-based routing still works (dev mode)
- ✅ DNS verification prevents domain hijacking
- ✅ SSL/TLS support for custom domains
- ✅ Admin panel for domain management
- ✅ Vendor isolation maintained
### Business Benefits:
- ✅ Enterprise feature for premium vendors
- ✅ Professional appearance for vendor shops
- ✅ Competitive advantage in market
- ✅ Higher vendor retention
- ✅ Additional revenue stream
---
## 📊 Implementation Summary
### Complexity: **Medium**
- New database table: 1
- New models: 1
- Modified files: 2-3
- New API endpoints: 5-7
### Time Required:
- **Quick test**: 30 minutes
- **Development**: 4-6 hours
- **Testing**: 2-3 hours
- **Production deployment**: 2-4 hours
- **Total**: 1-2 days
### Risk Level: **Low**
- Backward compatible
- No breaking changes
- Easy rollback
- Well-documented
### ROI: **High**
- Premium feature
- Low maintenance
- Scalable solution
- Competitive advantage
---
## 🔧 Technology Stack
Your existing stack works perfectly:
- ✅ FastAPI (Python web framework)
- ✅ PostgreSQL (database)
- ✅ SQLAlchemy (ORM)
- ✅ Jinja2 (templates)
- ✅ Alpine.js (frontend)
- ✅ Nginx (web server)
No new technologies required!
---
## 🆘 Support & Troubleshooting
### Common Questions
All answered in **EXECUTIVE_SUMMARY.md** (Q&A section)
### Common Issues
Listed in **QUICK_START.md** and **IMPLEMENTATION_CHECKLIST.md**
### Testing Problems
Covered in **CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md**
### Production Issues
See **IMPLEMENTATION_CHECKLIST.md** (Troubleshooting Guide)
---
## 📝 Key Concepts
### Multi-Tenant Architecture
Your app already supports this! Each vendor is a tenant with isolated data.
### Domain Mapping
New concept: Links custom domains to vendors via database table.
### Request Flow Priority
1. Custom domain (NEW)
2. Subdomain (EXISTING)
3. Path-based (EXISTING)
### DNS Verification
Security feature: Proves vendor owns the domain before activation.
### Vendor Isolation
Already working! Custom domains just add another entry point.
---
## 🎓 Learning Path
### Beginner (New to concept):
1. Read EXECUTIVE_SUMMARY.md
2. Look at ARCHITECTURE_DIAGRAMS.md
3. Try QUICK_START.md locally
### Intermediate (Ready to implement):
1. Follow QUICK_START.md
2. Read CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md
3. Use IMPLEMENTATION_CHECKLIST.md
### Advanced (Production deployment):
1. Review all documentation
2. Follow IMPLEMENTATION_CHECKLIST.md
3. Implement monitoring and maintenance
---
## 📈 Success Metrics
After implementation, you can track:
- Number of custom domains active
- Vendor adoption rate
- Domain verification success rate
- Traffic per domain
- SSL certificate status
- Failed domain lookups
---
## 🚦 Status Indicators
Throughout the documentation, you'll see these indicators:
✅ - Recommended approach
❌ - Not recommended
⚠️ - Warning or caution
🔧 - Technical detail
💡 - Tip or best practice
📝 - Note or important info
---
## 📞 Next Actions
### To Get Started:
1. Read EXECUTIVE_SUMMARY.md
2. Follow QUICK_START.md for local test
3. Review implementation approach with team
### To Deploy:
1. Complete QUICK_START.md test
2. Follow IMPLEMENTATION_CHECKLIST.md
3. Reference CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md
### To Understand:
1. Read EXECUTIVE_SUMMARY.md
2. Study ARCHITECTURE_DIAGRAMS.md
3. Review code files
---
## 📦 Package Contents
```
custom-domain-support/
├── Documentation/
│ ├── EXECUTIVE_SUMMARY.md (This provides overview)
│ ├── QUICK_START.md (30-minute implementation)
│ ├── CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md (Complete guide)
│ ├── ARCHITECTURE_DIAGRAMS.md (Visual diagrams)
│ ├── IMPLEMENTATION_CHECKLIST.md (Step-by-step tasks)
│ └── INDEX.md (This file)
└── Code Files/
├── vendor_domain_model.py (Database model)
├── updated_vendor_context.py (Enhanced middleware)
├── vendor_domains_api.py (Admin API)
├── migration_vendor_domains.py (Database migration)
├── config_updates.py (Configuration changes)
└── vendor_model_update.py (Vendor model updates)
```
---
## ✨ Final Notes
**This is a complete, production-ready solution.**
Everything you need is included:
- ✅ Complete documentation
- ✅ Working code examples
- ✅ Database migrations
- ✅ Security considerations
- ✅ Testing strategies
- ✅ Deployment guides
- ✅ Troubleshooting help
- ✅ Rollback plans
**Your current architecture is perfect for this feature!**
No major changes needed - just add domain mapping layer on top of existing vendor detection.
**Start with QUICK_START.md and you'll have a working demo in 30 minutes!**
---
**Questions?** Every document has troubleshooting and Q&A sections!
**Ready to begin?** Start with [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md) or jump straight to [QUICK_START.md](QUICK_START.md)!

View File

@@ -1,341 +0,0 @@
# Quick Start: Custom Domains in 30 Minutes
This guide gets you from zero to working custom domains in 30 minutes.
## What You're Building
**Before:**
- Vendors only accessible via `vendor1.platform.com`
**After:**
- Vendors accessible via custom domains: `customdomain1.com` → Vendor 1
- Old subdomain method still works!
## Prerequisites
- FastAPI application with vendor context middleware (you have this ✓)
- PostgreSQL database (you have this ✓)
- Admin privileges to add database tables
## 5-Step Setup
### Step 1: Add Database Table (5 minutes)
Create and run this migration:
```sql
-- Create vendor_domains table
CREATE TABLE vendor_domains (
id SERIAL PRIMARY KEY,
vendor_id INTEGER NOT NULL REFERENCES vendors(id) ON DELETE CASCADE,
domain VARCHAR(255) NOT NULL UNIQUE,
is_primary BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
is_verified BOOLEAN DEFAULT FALSE,
verification_token VARCHAR(100) UNIQUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_domain_active ON vendor_domains(domain, is_active);
```
**Verify:**
```bash
psql your_database -c "\d vendor_domains"
```
### Step 2: Create Model (5 minutes)
Create `models/database/vendor_domain.py`:
```python
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from app.core.database import Base
from datetime import datetime, timezone
class VendorDomain(Base):
__tablename__ = "vendor_domains"
id = Column(Integer, primary_key=True)
vendor_id = Column(Integer, ForeignKey("vendors.id", ondelete="CASCADE"))
domain = Column(String(255), nullable=False, unique=True)
is_primary = Column(Boolean, default=False)
is_active = Column(Boolean, default=True)
is_verified = Column(Boolean, default=False)
verification_token = Column(String(100))
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
vendor = relationship("Vendor", back_populates="domains")
@classmethod
def normalize_domain(cls, domain: str) -> str:
return domain.replace("https://", "").replace("http://", "").rstrip("/").lower()
```
Update `models/database/vendor.py`:
```python
# Add to Vendor class
domains = relationship("VendorDomain", back_populates="vendor")
```
### Step 3: Update Middleware (10 minutes)
Replace your `middleware/vendor_context.py` with this key section:
```python
from models.database.vendor_domain import VendorDomain
class VendorContextManager:
@staticmethod
def detect_vendor_context(request: Request) -> Optional[dict]:
host = request.headers.get("host", "").split(":")[0]
path = request.url.path
# NEW: Priority 1 - Custom domain check
from app.core.config import settings
platform_domain = getattr(settings, 'platform_domain', 'platform.com')
is_custom_domain = (
host and
not host.endswith(f".{platform_domain}") and
host != platform_domain and
"localhost" not in host and
not host.startswith("admin.")
)
if is_custom_domain:
return {
"domain": VendorDomain.normalize_domain(host),
"detection_method": "custom_domain",
"host": host
}
# EXISTING: Priority 2 - Subdomain check
if "." in host and not "localhost" in host:
parts = host.split(".")
if len(parts) >= 2 and parts[0] not in ["www", "admin", "api"]:
return {
"subdomain": parts[0],
"detection_method": "subdomain",
"host": host
}
# EXISTING: Priority 3 - Path-based check
if path.startswith("/vendor/"):
path_parts = path.split("/")
if len(path_parts) >= 3:
return {
"subdomain": path_parts[2],
"detection_method": "path",
"path_prefix": f"/vendor/{path_parts[2]}"
}
return None
@staticmethod
def get_vendor_from_context(db: Session, context: dict) -> Optional[Vendor]:
if not context:
return None
# NEW: Custom domain lookup
if context.get("detection_method") == "custom_domain":
vendor_domain = (
db.query(VendorDomain)
.filter(VendorDomain.domain == context["domain"])
.filter(VendorDomain.is_active == True)
.filter(VendorDomain.is_verified == True)
.first()
)
if vendor_domain and vendor_domain.vendor.is_active:
return vendor_domain.vendor
# EXISTING: Subdomain/path lookup
if "subdomain" in context:
return (
db.query(Vendor)
.filter(func.lower(Vendor.subdomain) == context["subdomain"].lower())
.filter(Vendor.is_active == True)
.first()
)
return None
```
### Step 4: Add Config (2 minutes)
Edit `app/core/config.py`:
```python
class Settings(BaseSettings):
# ... existing settings ...
# Add this
platform_domain: str = "platform.com" # Change to YOUR domain
```
Update `.env`:
```
PLATFORM_DOMAIN=platform.com # Change to YOUR domain
```
### Step 5: Test Locally (8 minutes)
**Add test data:**
```sql
-- Assuming you have a vendor with id=1
INSERT INTO vendor_domains (vendor_id, domain, is_active, is_verified)
VALUES (1, 'testdomain.local', true, true);
```
**Edit /etc/hosts:**
```bash
sudo nano /etc/hosts
# Add:
127.0.0.1 testdomain.local
```
**Start server:**
```bash
uvicorn main:app --reload
```
**Test in browser:**
```
http://testdomain.local:8000/
```
**Check logs for:**
```
✓ Vendor found via custom domain: testdomain.local → [Vendor Name]
```
## Done! 🎉
You now have custom domain support!
## Quick Test Checklist
- [ ] Can access vendor via custom domain: `testdomain.local:8000`
- [ ] Can still access via subdomain: `vendor1.localhost:8000`
- [ ] Can still access via path: `localhost:8000/vendor/vendor1/`
- [ ] Logs show correct detection method
- [ ] Database has vendor_domains table
- [ ] Model imports work
## What Works Now
**Custom domains** → Checks vendor_domains table
**Subdomains** → Checks vendors.subdomain (existing)
**Path-based** → Development mode (existing)
**Admin** → Still accessible
**Vendor isolation** → Each vendor sees only their data
## Next Steps
### For Development:
1. Test with multiple custom domains
2. Add more test vendors
3. Verify queries are scoped correctly
### For Production:
1. Add admin API endpoints (see full guide)
2. Add DNS verification (see full guide)
3. Configure Nginx to accept all domains
4. Set up SSL strategy
## Common Issues & Fixes
**Issue:** "Vendor not found"
```sql
-- Check if domain exists and is verified
SELECT * FROM vendor_domains WHERE domain = 'testdomain.local';
-- Should show is_verified = true and is_active = true
```
**Issue:** Wrong vendor loaded
```sql
-- Check for duplicate domains
SELECT domain, COUNT(*) FROM vendor_domains GROUP BY domain HAVING COUNT(*) > 1;
```
**Issue:** Still using subdomain detection
```python
# Check middleware logs - should show:
# detection_method: "custom_domain"
# Not "subdomain"
```
## Production Checklist
Before going live:
- [ ] Update `PLATFORM_DOMAIN` in production .env
- [ ] Configure Nginx: `server_name _;`
- [ ] Set up SSL strategy (Cloudflare recommended)
- [ ] Add proper DNS verification (security!)
- [ ] Add admin UI for domain management
- [ ] Test with real custom domain
- [ ] Monitor logs for errors
- [ ] Set up rollback plan
## Files Changed
**New files:**
- `models/database/vendor_domain.py`
**Modified files:**
- `middleware/vendor_context.py` (added custom domain logic)
- `app/core/config.py` (added platform_domain setting)
- `models/database/vendor.py` (added domains relationship)
**Database:**
- Added `vendor_domains` table
## Architecture Diagram
```
Request: customdomain1.com
Nginx: Accepts all domains
FastAPI Middleware:
Is it custom domain? YES
Query vendor_domains table
Find vendor_id = 1
Load Vendor 1
Route Handler:
Use request.state.vendor (Vendor 1)
All queries scoped to Vendor 1
Response: Vendor 1's shop
```
## Help & Resources
**Full Guides:**
- `/mnt/user-data/outputs/CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md` - Complete guide
- `/mnt/user-data/outputs/ARCHITECTURE_DIAGRAMS.md` - Visual diagrams
- `/mnt/user-data/outputs/IMPLEMENTATION_CHECKLIST.md` - Detailed checklist
**Files Generated:**
- `vendor_domain_model.py` - Complete model code
- `updated_vendor_context.py` - Complete middleware code
- `vendor_domains_api.py` - Admin API endpoints
- `migration_vendor_domains.py` - Database migration
## Timeline
- ✅ Step 1: 5 minutes (database)
- ✅ Step 2: 5 minutes (model)
- ✅ Step 3: 10 minutes (middleware)
- ✅ Step 4: 2 minutes (config)
- ✅ Step 5: 8 minutes (testing)
**Total: 30 minutes** ⏱️
---
**Questions?** Check the full implementation guide or review the architecture diagrams!

View File

@@ -1,425 +0,0 @@
# Custom Domain Support - Complete Implementation Package
## 🎯 Your Question Answered
**Q: "Can my FastAPI multi-tenant app support multiple shops on different custom domains like customdomain1.com and customdomain2.com?"**
**A: YES! ✅** Your architecture is **perfectly suited** for this. You just need to add a domain mapping layer.
---
## 📊 What You Have vs What You Need
### ✅ What You Already Have (Perfect!)
- Multi-tenant architecture with vendor isolation
- Vendor context middleware
- Subdomain routing: `vendor1.platform.com`
- Path-based routing: `/vendor/vendor1/`
- Request state management
### What You Need to Add (Simple!)
- Database table: `vendor_domains` (1 table)
- Enhanced middleware: Check custom domains first
- DNS verification: Prevent domain hijacking
---
## 🚀 Quick Start
```bash
# 1. Add database table (5 minutes)
CREATE TABLE vendor_domains (
id SERIAL PRIMARY KEY,
vendor_id INTEGER REFERENCES vendors(id),
domain VARCHAR(255) UNIQUE,
is_verified BOOLEAN DEFAULT FALSE
);
# 2. Update middleware (10 minutes)
# See: updated_vendor_context.py
# 3. Test locally (5 minutes)
# Add to /etc/hosts:
127.0.0.1 testdomain.local
# Add test data:
INSERT INTO vendor_domains (vendor_id, domain, is_verified)
VALUES (1, 'testdomain.local', true);
# Visit: http://testdomain.local:8000/
# ✅ Should show Vendor 1's shop!
```
**Total time: 30 minutes** ⏱️
---
## 🏗️ Architecture Overview
### Before (Current)
```
Customer → vendor1.platform.com → Middleware checks subdomain → Vendor 1
```
### After (With Custom Domains)
```
Customer → customdomain1.com → Middleware checks domain table → Vendor 1
(subdomain still works too!)
```
### How It Works
```
1. Request arrives: Host = "customdomain1.com"
2. Middleware: Is it custom domain? YES
3. Query: SELECT vendor_id FROM vendor_domains WHERE domain = 'customdomain1.com'
4. Result: vendor_id = 1
5. Load: Vendor 1 data
6. Render: Vendor 1's shop
```
---
## 📚 Documentation Structure
This package contains 6 comprehensive guides:
### Start Here → [INDEX.md](INDEX.md)
Master navigation guide to all documentation
### Quick Implementation → [QUICK_START.md](QUICK_START.md)
- ⏱️ 30 minutes to working demo
- 🔧 Hands-on implementation
- ✅ Local testing included
### Complete Guide → [CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md](CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md)
- 📖 Comprehensive documentation
- 🔐 Security best practices
- 🚀 Production deployment
- ⚠️ Troubleshooting guide
### Visual Learning → [ARCHITECTURE_DIAGRAMS.md](ARCHITECTURE_DIAGRAMS.md)
- 📊 Architecture diagrams
- 🔄 Request flow illustrations
- 🗄️ Database relationships
- 🌐 DNS configuration examples
### Project Planning → [IMPLEMENTATION_CHECKLIST.md](IMPLEMENTATION_CHECKLIST.md)
- ✅ Phase-by-phase tasks
- 🧪 Testing procedures
- 📦 Deployment steps
- 🔄 Rollback plan
### Decision Making → [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md)
- 💼 Business case
- 💰 ROI analysis
- ⚖️ Risk assessment
- ❓ Q&A section
---
## 💻 Code Files Included
All ready-to-use code in `/home/claude/`:
| File | Purpose | Status |
|------|---------|--------|
| `vendor_domain_model.py` | SQLAlchemy model | ✅ Ready |
| `updated_vendor_context.py` | Enhanced middleware | ✅ Ready |
| `vendor_domains_api.py` | Admin API endpoints | ✅ Ready |
| `migration_vendor_domains.py` | Database migration | ✅ Ready |
| `config_updates.py` | Configuration | ✅ Ready |
| `vendor_model_update.py` | Vendor model update | ✅ Ready |
---
## 🎯 Key Features
### ✅ What You Get
**Custom Domain Support:**
- Vendors can use their own domains
- Multiple domains per vendor
- Professional branding
**Security:**
- DNS verification required
- Prevents domain hijacking
- Vendor ownership proof
**Backward Compatible:**
- Subdomain routing still works
- Path-based routing still works
- No breaking changes
**Scalability:**
- Single server handles all domains
- Database-driven routing
- Easy to manage
---
## 📈 Implementation Metrics
### Complexity
- **Database**: +1 table
- **Models**: +1 new, ~1 modified
- **Middleware**: ~1 file modified
- **Endpoints**: +5-7 API routes
- **Risk Level**: 🟢 Low
### Time Required
- **Local Test**: 30 minutes
- **Development**: 4-6 hours
- **Testing**: 2-3 hours
- **Deployment**: 2-4 hours
- **Total**: 🕐 1-2 days
### Resources
- **New Technologies**: None! Use existing stack
- **Server Changes**: Nginx config update
- **DNS Required**: Yes, per custom domain
---
## 🔐 Security Features
-**DNS Verification**: Vendors must prove domain ownership
-**TXT Record Check**: Token-based verification
-**Vendor Isolation**: Each vendor sees only their data
-**Active Status**: Domains can be deactivated
-**Audit Trail**: Track domain changes
---
## 🌐 DNS Configuration (Vendor Side)
When a vendor wants to use `customdomain1.com`:
```
# At their domain registrar:
1. A Record:
Name: @
Value: 123.45.67.89 (your server IP)
2. Verification TXT Record:
Name: _wizamart-verify
Value: [token from your platform]
3. Wait 5-15 minutes for DNS propagation
4. Admin verifies domain
5. Domain goes live! 🎉
```
---
## 🚀 Deployment Options
### Development
```bash
# Test locally with /etc/hosts
127.0.0.1 testdomain.local
```
### Staging
```bash
# Use subdomain for testing
staging-vendor.platform.com
```
### Production
```bash
# Full custom domain support
customdomain1.com → Vendor 1
customdomain2.com → Vendor 2
```
---
## 💡 Why This Works
Your architecture is **already multi-tenant**:
- ✅ Vendor isolation exists
- ✅ Middleware detects context
- ✅ All queries scoped to vendor
- ✅ Request state management works
You just need to add **one more detection method**:
1. ~~Check subdomain~~ (existing)
2. ~~Check path~~ (existing)
3. **Check custom domain** (new!)
---
## 📦 What's Included
### Complete Documentation
- 5 detailed guides (70+ pages)
- Architecture diagrams
- Implementation checklist
- Quick start guide
- Troubleshooting help
### Production-Ready Code
- Database models
- API endpoints
- Migrations
- Configuration
- Middleware updates
### Best Practices
- Security guidelines
- Testing strategies
- Deployment procedures
- Monitoring setup
- Rollback plans
---
## 🎓 Learning Path
### New to Concept? (30 minutes)
1. Read: EXECUTIVE_SUMMARY.md
2. View: ARCHITECTURE_DIAGRAMS.md
3. Try: QUICK_START.md
### Ready to Build? (2-3 hours)
1. Follow: QUICK_START.md
2. Reference: CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md
3. Use: Code files provided
### Deploying to Production? (1-2 days)
1. Complete: QUICK_START.md test
2. Follow: IMPLEMENTATION_CHECKLIST.md
3. Reference: All documentation
---
## ✅ Success Criteria
After implementation:
- [ ] Custom domains route to correct vendors
- [ ] Subdomain routing still works
- [ ] Path-based routing still works
- [ ] DNS verification functional
- [ ] SSL certificates work
- [ ] Admin can manage domains
- [ ] Monitoring in place
- [ ] Documentation complete
---
## 🎉 Benefits
### For You (Platform Owner)
- 💰 Premium feature for vendors
- 🏆 Competitive advantage
- 📈 Higher vendor retention
- 🔧 Single codebase maintained
### For Vendors
- 🎨 Own brand domain
- 🔍 Better SEO
- 💼 Professional appearance
- 📊 Marketing benefits
### For Customers
- ✨ Seamless experience
- 🔒 Trust familiar domain
- 🎯 Better UX
---
## 📞 Next Steps
### To Understand
→ Read [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md)
### To Test Locally
→ Follow [QUICK_START.md](QUICK_START.md)
### To Implement
→ Use [IMPLEMENTATION_CHECKLIST.md](IMPLEMENTATION_CHECKLIST.md)
### To Deploy
→ Reference [CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md](CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md)
### To Navigate Everything
→ Check [INDEX.md](INDEX.md)
---
## ❓ Common Questions
**Q: Will this break my existing setup?**
A: No! Completely backward compatible.
**Q: Do I need new servers?**
A: No! Single server handles all domains.
**Q: What about SSL certificates?**
A: Use Cloudflare (easiest) or Let's Encrypt.
**Q: How long to implement?**
A: 30 minutes for demo, 1-2 days for production.
**Q: Is it secure?**
A: Yes! DNS verification prevents hijacking.
**Q: Can one vendor have multiple domains?**
A: Yes! Fully supported.
More Q&A in [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md)
---
## 🏁 Final Thoughts
**Your architecture is perfect for this!**
You have:
- ✅ Multi-tenant design
- ✅ Vendor isolation
- ✅ Context middleware
- ✅ FastAPI + PostgreSQL
You just need:
- Domain mapping table
- Enhanced middleware
- DNS verification
**It's simpler than you think!**
---
## 📋 File Locations
### Documentation (read online)
- `/mnt/user-data/outputs/INDEX.md`
- `/mnt/user-data/outputs/EXECUTIVE_SUMMARY.md`
- `/mnt/user-data/outputs/QUICK_START.md`
- `/mnt/user-data/outputs/CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md`
- `/mnt/user-data/outputs/ARCHITECTURE_DIAGRAMS.md`
- `/mnt/user-data/outputs/IMPLEMENTATION_CHECKLIST.md`
### Code Files (copy to your project)
- `/home/claude/vendor_domain_model.py`
- `/home/claude/updated_vendor_context.py`
- `/home/claude/vendor_domains_api.py`
- `/home/claude/migration_vendor_domains.py`
- `/home/claude/config_updates.py`
- `/home/claude/vendor_model_update.py`
---
**Ready to start?**
Begin with [QUICK_START.md](QUICK_START.md) for a 30-minute working demo!
Or read [INDEX.md](INDEX.md) for complete navigation guide.
---
**Good luck! 🚀**

View File

@@ -1,287 +0,0 @@
╔══════════════════════════════════════════════════════════════════════════════╗
║ WIZAMART MULTI-TENANT ARCHITECTURE ║
╚══════════════════════════════════════════════════════════════════════════════╝
┌──────────────────────────────────────────────────────────────────────────────┐
│ PRODUCTION DEPLOYMENT │
└──────────────────────────────────────────────────────────────────────────────┘
Internet
│ HTTPS
┌──────────────┼──────────────┐
│ │ │
┌───────────▼─────────┐ │ ┌────────▼────────────┐
│ │ │ │ │
│ acme.wizamart.com │ │ │ store.acme-corp.com │
│ (Subdomain Mode) │ │ │ (Custom Domain) │
│ │ │ │ │
└───────────┬─────────┘ │ └────────┬────────────┘
│ │ │
└──────────────┼────────────┘
│ HTTP Request
┌──────────────────────────┐
│ FastAPI Application │
│ (main.py) │
└──────────────────────────┘
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Middleware │ │ Middleware │ │ Middleware │
│ Chain │ │ Chain │ │ Chain │
├────────────────┤ ├──────────────┤ ├──────────────────┤
│1. CORS │ │1. CORS │ │1. CORS │
│2. Vendor │ │2. Vendor │ │2. Vendor │
│ Detection │ │ Detection │ │ Detection │
│3. Context │ │3. Context │ │3. Context │
│ Detection │ │ Detection │ │ Detection │
│4. Theme │ │4. Theme │ │4. Theme │
│ Loading │ │ Loading │ │ Loading │
│5. Logging │ │5. Logging │ │5. Logging │
└────────────────┘ └──────────────┘ └──────────────────┘
│ │ │
│ request.state: │ request.state: │ request.state:
│ vendor=1 │ vendor=1 │ vendor=2
│ theme={...} │ theme={...} │ theme={...}
│ context=SHOP │ context=SHOP │ context=SHOP
│ │ │
└──────────────────┼──────────────────┘
┌──────────▼──────────┐
│ Route Matching │
│ /shop/products │
└──────────┬──────────┘
┌──────────▼──────────────────┐
│ Handler Execution │
│ (shop_pages.py) │
└──────────┬──────────────────┘
┌──────────▼──────────────────┐
│ Template Rendering │
│ (shop/products.html) │
│ + Vendor Theme │
│ + Vendor Name │
└──────────┬──────────────────┘
┌──────────────▼──────────────┐
│ Jinja2 Template │
│ ├─ ACME's theme colors │
│ ├─ ACME's logo │
│ ├─ ACME's fonts │
│ └─ Data placeholder │
└──────────┬────────────────┘
┌──────▼──────┐
│ HTML Output │
│ + Alpine.js │
└──────┬──────┘
Browser renders
JavaScript runs
API calls:
/api/v1/public/vendors/1/products
┌──────▼──────────────┐
│ Backend API │
│ ├─ products table │
│ │ (vendor_id=1) │
│ └─ returns JSON │
└──────┬──────────────┘
┌──────▼──────────────┐
│ Products rendered │
│ with ACME's theme │
│ in browser │
└────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ DEVELOPMENT DEPLOYMENT (Path-Based Routing) │
└──────────────────────────────────────────────────────────────────────────────┘
Browser
│ http://localhost:8000/vendors/acme/shop/products
┌───────────────────────────┐
│ FastAPI on Localhost │
│ (main.py) │
└──────────┬────────────────┘
│ Middleware detects:
│ ├─ Path: /vendors/acme/...
│ ├─ Extraction: vendor="acme"
│ ├─ Query: vendors WHERE subdomain='acme'
│ └─ Sets: request.state.vendor=Vendor(1)
┌──────▼──────────────────┐
│ Remaining middleware │
│ (context, theme, etc.) │
└──────┬──────────────────┘
┌──────▼──────────────────┐
│ Route: /shop/products │
│ (depends on path rewrite│
│ middleware) │
└──────┬──────────────────┘
┌──────▼──────────────────┐
│ Same HTML rendering │
│ Same API calls │
│ Same result │
└────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ VENDOR DETECTION DECISION TREE │
└──────────────────────────────────────────────────────────────────────────────┘
Incoming Request
Extract Host
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
Is custom Is subdomain? Is path-based?
domain? (not www, not (/vendors/...?)
(not *. admin, not api)
platform.com)
│ │ │
YES YES YES
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Lookup │ │Extract │ │Extract │
│vendor_ │ │subdomain│ │subdomain│
│domains │ │from host│ │from path│
│table │ └────┬────┘ └────┬────┘
└────┬────┘ │ │
│ ┌───────▼─────────┐ ┌─────▼──────┐
│ │Query vendors │ │Query vendors│
│ │WHERE subdomain= │ │WHERE subdom│
│ │'{subdomain}' │ │='{code}' │
│ └───────┬─────────┘ └─────┬──────┘
│ │ │
└──────────────┼───────────────────┘
┌───────▼────────┐
│ Vendor Found? │
└───┬────────┬───┘
YES NO
│ │
▼ ▼
┌───────────┐ ┌──────────┐
│Set │ │Return 404│
│request. │ │(Vendor │
│state. │ │not found)│
│vendor │ └──────────┘
└───────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ DATABASE SCHEMA (Multi-Tenant Isolation) │
└──────────────────────────────────────────────────────────────────────────────┘
vendors
├─ id (PK)
├─ name
├─ subdomain (unique)
├─ is_active
└─ ... (other fields)
vendor_domains
├─ id (PK)
├─ vendor_id (FK → vendors.id)
├─ domain (unique)
├─ is_active
├─ is_verified
└─ ... (other fields)
vendor_themes
├─ id (PK)
├─ vendor_id (FK → vendors.id)
├─ theme_name
├─ colors (JSON)
├─ branding (JSON)
├─ fonts (JSON)
├─ is_active
└─ ... (other fields)
products
├─ id (PK)
├─ vendor_id (FK → vendors.id) ← MULTI-TENANT KEY
├─ name
├─ description
├─ price
├─ is_active
└─ ... (other fields)
orders
├─ id (PK)
├─ vendor_id (FK → vendors.id) ← MULTI-TENANT KEY
├─ customer_id
├─ total
└─ ... (other fields)
... Every table has vendor_id to enforce isolation
┌──────────────────────────────────────────────────────────────────────────────┐
│ REQUEST CONTEXT DETECTION │
└──────────────────────────────────────────────────────────────────────────────┘
Path Context Type Handler
──────────────────────────────── ───────────────── ──────────────────────
/api/v1/admin/users → RequestContext.API (JSON endpoints)
/api/v1/vendor/products → RequestContext.API
/api/v1/public/products → RequestContext.API
/admin/dashboard → RequestContext.ADMIN (HTML templates)
/admin/users → RequestContext.ADMIN
/vendor/dashboard → RequestContext.VENDOR_DASHBOARD
/vendor/products → RequestContext.VENDOR_DASHBOARD
/shop/products → RequestContext.SHOP (with vendor context)
/shop/products/123 → RequestContext.SHOP
/ → RequestContext.FALLBACK
┌──────────────────────────────────────────────────────────────────────────────┐
│ AUTHORIZATION LAYERS │
└──────────────────────────────────────────────────────────────────────────────┘
Layer 1: URL Detection
Vendor identified from host/domain/path
Layer 2: Middleware
request.state.vendor = Vendor(...)
Layer 3: Database Queries
SELECT * FROM products WHERE vendor_id = request.state.vendor.id
Layer 4: API Authorization
if endpoint_vendor_id != request.state.vendor.id:
raise UnauthorizedShopAccessException()
Result: Complete vendor isolation at all levels
No way to cross-access vendor data
╔══════════════════════════════════════════════════════════════════════════════╗
║ END OF DIAGRAM ║
╚══════════════════════════════════════════════════════════════════════════════╝

View File

@@ -1,477 +0,0 @@
╔══════════════════════════════════════════════════════════════════════════════╗
║ WIZAMART MULTI-TENANT URL ROUTING - COMPLETE SUMMARY ║
╚══════════════════════════════════════════════════════════════════════════════╝
TL;DR
═════════════════════════════════════════════════════════════════════════════
Q: What's the URL for a customer to connect to a vendor's shop in Wizamart?
A: It depends on the deployment mode:
1. PRODUCTION (Subdomain): https://vendor-name.wizamart.com/shop/products
2. PRODUCTION (Custom Domain): https://vendor-domain.com/shop/products
3. DEVELOPMENT (Path-Based): http://localhost:8000/vendors/vendor-code/shop/products
THE COMPLETE PICTURE
═════════════════════════════════════════════════════════════════════════════
DEPLOYMENT MODES
────────────────
┌─ Mode 1: SUBDOMAIN ─────────────────────────────┐
│ │
│ URL: https://acme.wizamart.com/shop/products │
│ │
│ How it works: │
│ 1. Customer visits acme.wizamart.com │
│ 2. Middleware detects subdomain "acme" │
│ 3. Queries: vendors WHERE subdomain = 'acme' │
│ 4. Finds Vendor(id=1, name="ACME Store") │
│ 5. Loads ACME's theme │
│ 6. Renders HTML with ACME's branding │
│ │
│ Best for: Production, standard vendors │
│ SSL: *.wizamart.com wildcard cert │
│ DNS: Add subdomains as needed │
└─────────────────────────────────────────────────┘
┌─ Mode 2: CUSTOM DOMAIN ─────────────────────────┐
│ │
│ URL: https://store.acmecorp.com/shop/products │
│ │
│ How it works: │
│ 1. Customer visits store.acmecorp.com │
│ 2. Middleware detects custom domain │
│ 3. Queries: vendor_domains WHERE domain = ... │
│ 4. Finds VendorDomain(vendor_id=1) │
│ 5. Joins to Vendor(id=1) │
│ 6. Loads ACME's theme │
│ 7. Renders HTML with ACME's branding │
│ │
│ Best for: Production, premium vendors │
│ SSL: Vendor's own SSL certificate │
│ DNS: Vendor configures domain │
└─────────────────────────────────────────────────┘
┌─ Mode 3: PATH-BASED ────────────────────────────┐
│ │
│ URL: http://localhost:8000/vendor/acme/shop... │
│ │
│ How it works: │
│ 1. Developer visits localhost:8000/vendor/.. │
│ 2. Middleware detects path-based routing │
│ 3. Extracts "acme" from path │
│ 4. Queries: vendors WHERE subdomain = 'acme' │
│ 5. Finds Vendor(id=1) │
│ 6. Loads ACME's theme │
│ 7. Renders HTML with ACME's branding │
│ │
│ Best for: Local development & testing │
│ SSL: None needed (localhost) │
│ DNS: None needed │
└─────────────────────────────────────────────────┘
REQUEST PROCESSING FLOW
═══════════════════════
Customer Request
├─ vendor_context_middleware
│ ├─ Detects vendor from host/domain/path
│ └─ Sets request.state.vendor = Vendor(...)
├─ context_middleware
│ ├─ Detects RequestContext.SHOP
│ └─ Sets request.state.context_type
├─ theme_context_middleware
│ ├─ Loads vendor theme
│ └─ Sets request.state.theme
├─ Route Matching → /shop/products
├─ Handler Execution → shop_products_page()
├─ Template Rendering → shop/products.html
│ ├─ Uses vendor.name → "ACME Store"
│ ├─ Uses theme.colors → "#FF6B6B"
│ └─ Uses theme.logo → "acme-logo.png"
├─ HTML Sent to Browser
├─ JavaScript Runs
├─ API Call → /api/v1/public/vendors/1/products
├─ Backend Returns Products
├─ JavaScript Renders with Theme
Response: Fully Branded Shop
VENDOR ISOLATION: 4 LAYERS
════════════════════════════
Layer 1: URL DETECTION
├─ Subdomain → extract from host
├─ Custom Domain → lookup VendorDomain table
└─ Path → extract from URL path
Result: Vendor identified
Layer 2: MIDDLEWARE
└─ request.state.vendor = Vendor(...)
Result: Vendor accessible throughout request
Layer 3: DATABASE QUERIES
└─ WHERE vendor_id = request.state.vendor.id
Result: ONLY that vendor's data returned
Layer 4: API AUTHORIZATION
└─ if vendor_id != request.state.vendor.id: raise Exception
Result: Cross-vendor access impossible
ALL SHOP ROUTES
════════════════
PUBLIC ROUTES (No authentication required)
───────────────────────────────────────────
/shop/ → Homepage / product catalog
/shop/products → Product catalog
/shop/products/{id} → Product detail page
/shop/categories/{slug} → Category products
/shop/cart → Shopping cart
/shop/checkout → Checkout process
/shop/search → Search results
/shop/about → About us page
/shop/contact → Contact us page
/shop/faq → FAQ page
/shop/privacy → Privacy policy
/shop/terms → Terms of service
AUTHENTICATION ROUTES
──────────────────────
/shop/account/register → Customer registration
/shop/account/login → Customer login
/shop/account/forgot-password → Password reset
PROTECTED ROUTES (Authentication required)
───────────────────────────────────────────
/shop/account/ → Account home
/shop/account/dashboard → Account dashboard
/shop/account/orders → Order history
/shop/account/orders/{id} → Order details
/shop/account/profile → Profile page
/shop/account/addresses → Address management
/shop/account/wishlist → Wishlist
/shop/account/settings → Account settings
COMPLETE REQUEST LIFECYCLE EXAMPLE
═══════════════════════════════════
Scenario: Customer visits https://acme.wizamart.com/shop/products
Step 1: Browser Request
GET https://acme.wizamart.com/shop/products
Headers: Host: acme.wizamart.com
Step 2: vendor_context_middleware
Input: host = "acme.wizamart.com"
Processing:
1. Check if custom domain? NO
2. Check if subdomain? YES
3. Extract "acme"
4. Query: vendors WHERE subdomain = 'acme'
Output: request.state.vendor = Vendor(id=1, name="ACME Store")
Step 3: context_middleware
Input: path = "/shop/products", request.state.vendor exists
Processing:
1. Is /api/*? NO
2. Is /admin/*? NO
3. Has vendor? YES
Output: request.state.context_type = RequestContext.SHOP
Step 4: theme_context_middleware
Input: request.state.vendor.id = 1
Processing:
1. Query: vendor_themes WHERE vendor_id = 1
Output: request.state.theme = {colors, branding, fonts}
Step 5: Route Matching
Path: /shop/products
Matches: GET /shop/products
Handler: shop_products_page(request)
Step 6: Template Rendering (shop/products.html)
Variables available:
- request.state.vendor.name → "ACME Store"
- request.state.theme.colors.primary → "#FF6B6B"
- request.state.theme.branding.logo → "acme-logo.png"
Step 7: HTML Response
<!DOCTYPE html>
<html>
<head>
<style>
:root {
--color-primary: #FF6B6B;
--color-secondary: #FF8787;
}
</style>
</head>
<body>
<img src="acme-logo.png" />
<h1>ACME Store</h1>
<div id="products"></div>
<script>
fetch('/api/v1/public/vendors/1/products')
.then(r => r.json())
.then(data => renderProducts(data))
</script>
</body>
</html>
Step 8: Browser Renders
- JavaScript fetches products via API
- Products rendered with theme colors
- User sees fully branded ACME store
KEY DIFFERENCES BETWEEN MODES
═══════════════════════════════
SUBDOMAIN:
✓ Easy to manage
✓ Single SSL certificate
✓ Customers need no setup
✗ All on same domain
✗ Less professional branding
CUSTOM DOMAIN:
✓ Professional branding
✓ Vendor's own domain
✓ Better for premium vendors
✗ Each vendor needs SSL
✗ Vendor must configure domain
PATH-BASED:
✓ Perfect for development
✓ No DNS setup needed
✓ Test multiple vendors
✗ Not suitable for production
✗ All vendors share localhost
AUTHENTICATION
════════════════
Customer Authentication:
- JWT token in Authorization header (API calls)
- customer_token cookie (page navigation)
- Cookie path scoped to /shop (vendor isolation)
- HttpOnly flag prevents JavaScript access
Example:
Set-Cookie: customer_token=eyJ...; Path=/shop; HttpOnly; SameSite=Lax
API ENDPOINTS
════════════════
All public endpoints follow this pattern:
GET /api/v1/public/vendors/{vendor_id}/products
GET /api/v1/public/vendors/{vendor_id}/products/{product_id}
GET /api/v1/public/vendors/{vendor_id}/products/search
POST /api/v1/public/vendors/{vendor_id}/cart
...
These work from ANY domain because vendor_id is explicit in the URL.
DATABASE MULTI-TENANT KEYS
════════════════════════════
Every business table has vendor_id:
products
├─ id
├─ vendor_id ← Required
├─ name
└─ ...
orders
├─ id
├─ vendor_id ← Required
├─ customer_id
└─ ...
reviews
├─ id
├─ vendor_id ← Required
├─ product_id
└─ ...
This ensures query filtering at database level.
MIDDLEWARE DETAILS
═══════════════════
File: middleware/vendor_context.py
Purpose: Detect vendor from host/domain/path
Methods:
- detect_vendor_context() → Identifies vendor
- get_vendor_from_context() → Looks up vendor
- extract_clean_path() → Extracts path (dev mode)
- is_admin_request() → Checks if admin
- is_api_request() → Checks if API
- is_static_file_request() → Checks if static
File: middleware/theme_context.py
Purpose: Load vendor-specific theme
Methods:
- get_vendor_theme() → Loads theme from DB
- get_default_theme() → Returns fallback theme
File: middleware/context_middleware.py
Purpose: Determine request context type
Types:
- RequestContext.API → /api/* endpoints
- RequestContext.ADMIN → /admin/* endpoints
- RequestContext.VENDOR_DASHBOARD → /vendor/* endpoints
- RequestContext.SHOP → Customer shop
- RequestContext.FALLBACK → Unknown
File: middleware/logging_middleware.py
Purpose: Log all requests and responses
DEBUGGING TIPS
═════════════
Check Vendor Detection:
{{ request.state.vendor.name }}
{{ request.state.vendor.subdomain }}
Check Theme Loading:
{{ request.state.theme.colors.primary }}
{{ request.state.theme.branding.logo }}
Check Context Type:
{{ request.state.context_type }}
Server Logs:
[OK] Vendor found via subdomain: acme -> ACME Store
[WARNING] Vendor not found for context: {...}
Test with curl:
curl -H "Host: acme.wizamart.com" http://localhost:8000/shop/products
POTENTIAL ISSUES
═════════════════
Issue 1: Path-Based Routing Not Working
Problem: /vendor/acme/shop/products → 404
Cause: clean_path set but not used for routing
Solution: Add path rewriting middleware or dual route registration
Issue 2: Theme Not Loading
Problem: Products show without vendor theme
Cause: VendorTheme not created for vendor
Solution: Create VendorTheme in database or create default
Issue 3: Cross-Vendor Data Leakage
Problem: Customers can see other vendors' products
Cause: Missing vendor_id WHERE clause
Solution: Ensure all queries include vendor_id filter
Issue 4: Authentication Confusion
Problem: Tokens work across vendors
Cause: No cookie path isolation
Solution: Ensure cookie path is /shop per deployment
SECURITY BEST PRACTICES
═════════════════════════
✓ DO:
- Filter every database query by vendor_id
- Validate vendor_id in all API endpoints
- Use vendor-scoped authentication cookies
- Implement multi-layer authorization checks
- Log all vendor context switches
✗ DON'T:
- Trust vendor_id from request body alone
- Skip vendor checks in nested calls
- Mix vendors in single query
- Cache data without vendor isolation
- Assume middleware always runs
DEPLOYMENT CHECKLIST
════════════════════
For Production:
☐ Set up DNS with wildcard subdomain (*.wizamart.com)
☐ Or allow vendors to add custom domains
☐ Configure SSL certificates
☐ Set platform_domain in config
☐ Create vendors with unique subdomains
☐ Create VendorTheme for each vendor
☐ Test multi-vendor setup
☐ Verify no data leakage
☐ Enable logging
For Development:
☐ Use localhost:8000 with path-based routing
☐ Create test vendors
☐ Create test themes
☐ Test with multiple vendors
☐ Verify middleware detection
☐ Check database filters
METRICS TO MONITOR
═══════════════════
- Request/vendor distribution
- API latency by vendor
- Database query performance
- Middleware execution time
- Theme loading time
- Error rates by vendor
- Authentication failures
- Cross-vendor access attempts
SCALING CONSIDERATIONS
═══════════════════════
- Database: Partition by vendor_id if > 10k vendors
- Cache: Cache themes by vendor_id
- CDN: Serve static assets globally
- API Gateway: Route by vendor subdomain
- Monitoring: Alert on cross-vendor attempts
- Rate limiting: Per-vendor rate limits
═══════════════════════════════════════════════════════════════════════════════
For more details, see:
- WIZAMART_MULTITENANT_URL_GUIDE.md (Complete guide)
- QUICK_REFERENCE.md (Quick reference)
- ARCHITECTURE_DIAGRAMS.txt (Visual diagrams)
Generated: November 7, 2025
Version: Current Development

View File

@@ -1,283 +0,0 @@
╔══════════════════════════════════════════════════════════════════════════════╗
║ DOCUMENTATION GENERATION COMPLETE ✅ ║
║ ║
║ Wizamart Multi-Tenant URL Routing - Complete Guide ║
╚══════════════════════════════════════════════════════════════════════════════╝
📊 ANSWER TO YOUR QUESTION
═════════════════════════════════════════════════════════════════════════════
Q: What's the URL for a customer to connect to a vendor's shop in Wizamart?
A: It depends on the deployment mode:
1⃣ PRODUCTION (Subdomain)
https://acme.wizamart.com/shop/products
https://techpro.wizamart.com/shop/categories/electronics
https://fashion.wizamart.com/shop/cart
2⃣ PRODUCTION (Custom Domain - Premium)
https://store.acme-corp.com/shop/products
https://shop.techpro.io/shop/checkout
https://mybrand.com/shop/account/dashboard
3⃣ DEVELOPMENT (Path-Based)
http://localhost:8000/vendor/acme/shop/products
http://localhost:8000/vendor/techpro/shop/cart
http://localhost:8000/vendor/fashion/shop/checkout
📚 COMPLETE DOCUMENTATION GENERATED
═════════════════════════════════════════════════════════════════════════════
✅ 5 Comprehensive Documentation Files Created:
┌─ INDEX.md ──────────────────────────────────────────────────────┐
│ │
│ 📍 START HERE - Navigation guide to all documentation │
│ ├─ Quick navigation by use case │
│ ├─ Answers to common questions │
│ ├─ Debugging checklist │
│ └─ Quick reference card │
│ │
│ Size: 12 KB │
│ Read Time: 5-10 minutes │
└──────────────────────────────────────────────────────────────────┘
┌─ COMPLETE_SUMMARY.txt ───────────────────────────────────────────┐
│ │
│ 🎯 THE COMPLETE PICTURE │
│ ├─ TL;DR answer │
│ ├─ All three deployment modes │
│ ├─ Request processing flow │
│ ├─ 4-layer vendor isolation explanation │
│ ├─ Complete request lifecycle (step-by-step) │
│ ├─ All shop routes │
│ ├─ Debugging tips │
│ ├─ Security best practices │
│ ├─ Deployment checklist │
│ └─ Potential issues & solutions │
│ │
│ Size: 17 KB │
│ Read Time: 20-30 minutes │
│ Best For: Complete technical overview │
└──────────────────────────────────────────────────────────────────┘
┌─ WIZAMART_MULTITENANT_URL_GUIDE.md ──────────────────────────────┐
│ │
│ 📖 DETAILED TECHNICAL GUIDE │
│ ├─ Subdomain mode (production standard) │
│ ├─ Custom domain mode (production premium) │
│ ├─ Path-based mode (development) │
│ ├─ URL breakdown & examples │
│ ├─ Database perspective │
│ ├─ Real-world customer URLs │
│ ├─ API endpoints (vendor-independent) │
│ ├─ Theme integration │
│ ├─ Multi-layer vendor isolation │
│ └─ Potential issues & solutions │
│ │
│ Size: 16 KB │
│ Read Time: 25-35 minutes │
│ Best For: Deep technical understanding │
└──────────────────────────────────────────────────────────────────┘
┌─ QUICK_REFERENCE.md ─────────────────────────────────────────────┐
│ │
│ ⚡ QUICK LOOKUP GUIDE │
│ ├─ URL breakdown diagrams │
│ ├─ Request flow (in order) │
│ ├─ Detection priority │
│ ├─ All shop routes table │
│ ├─ Request state contents │
│ ├─ Common scenarios │
│ ├─ Debugging tips │
│ ├─ Quick links to code files │
│ └─ Environment configuration │
│ │
│ Size: 9.8 KB │
│ Read Time: 5-15 minutes │
│ Best For: Quick lookups while coding │
└──────────────────────────────────────────────────────────────────┘
┌─ ARCHITECTURE_DIAGRAMS.txt ──────────────────────────────────────┐
│ │
│ 🏗️ VISUAL ARCHITECTURE REFERENCE │
│ ├─ Production deployment diagram │
│ ├─ Development deployment diagram │
│ ├─ Vendor detection decision tree │
│ ├─ Database schema (multi-tenant) │
│ ├─ Request context detection table │
│ ├─ Authorization layers diagram │
│ └─ Complete request lifecycle visualization │
│ │
│ Size: 18 KB │
│ Read Time: 10-15 minutes │
│ Best For: Visual learners │
└──────────────────────────────────────────────────────────────────┘
🎯 QUICK START GUIDE
═════════════════════════════════════════════════════════════════════════════
For Developers (30 minutes):
1. Read: INDEX.md (5 min) → Get oriented
2. Read: COMPLETE_SUMMARY.txt top section (5 min) → Understand modes
3. View: ARCHITECTURE_DIAGRAMS.txt (5 min) → See the flow
4. Browse: QUICK_REFERENCE.md (10 min) → Learn details
5. Start coding! ✅
For DevOps (20 minutes):
1. Read: COMPLETE_SUMMARY.txt (10 min) → Understand all modes
2. Read: QUICK_REFERENCE.md → Deployment Checklist (5 min)
3. Follow the checklist for your chosen mode (5 min)
For Architects (15 minutes):
1. View: ARCHITECTURE_DIAGRAMS.txt (5 min) → See the design
2. Read: INDEX.md (5 min) → Understand the options
3. Decide on deployment mode ✅
For Quick Reference:
→ Use: QUICK_REFERENCE.md → Find what you need
⚙️ HOW THE SYSTEM WORKS
═════════════════════════════════════════════════════════════════════════════
Customer visits: https://acme.wizamart.com/shop/products
┌──────────────────────────────────────────────────────┐
│ FastAPI Middleware Chain │
├──────────────────────────────────────────────────────┤
│ 1. vendor_context_middleware │
│ ├─ Detects host: "acme.wizamart.com" │
│ ├─ Extracts subdomain: "acme" │
│ ├─ Queries: vendors WHERE subdomain = 'acme' │
│ └─ Sets: request.state.vendor = Vendor(ACME) │
│ │
│ 2. context_middleware │
│ └─ Sets: request.state.context_type = SHOP │
│ │
│ 3. theme_context_middleware │
│ ├─ Queries: vendor_themes WHERE vendor_id = 1 │
│ └─ Sets: request.state.theme = {...ACME theme...}│
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ Route Handler │
│ shop_products_page(request) │
│ → Returns: shop/products.html │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ Template Rendering (Jinja2) │
│ ├─ {{ request.state.vendor.name }} │
│ │ → "ACME Store" │
│ ├─ {{ request.state.theme.colors.primary }} │
│ │ → "#FF6B6B" │
│ └─ {{ request.state.theme.branding.logo }} │
│ → "acme-logo.png" │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ HTML Response + JavaScript │
│ fetch('/api/v1/public/vendors/1/products') │
│ → Renders products with ACME's theme │
└──────────────────────────────────────────────────────┘
✅ Customer sees fully branded ACME shop
📁 FILES SAVED TO: /mnt/user-data/outputs/
═════════════════════════════════════════════════════════════════════════════
📄 INDEX.md (12 KB)
↓ START HERE - Navigation & common Q&A
📄 COMPLETE_SUMMARY.txt (17 KB)
↓ Complete technical overview with examples
📄 WIZAMART_MULTITENANT_URL_GUIDE.md (16 KB)
↓ Detailed technical guide for all modes
📄 QUICK_REFERENCE.md (9.8 KB)
↓ Quick lookup while coding
📄 ARCHITECTURE_DIAGRAMS.txt (18 KB)
↓ Visual diagrams & flowcharts
💡 KEY TAKEAWAYS
═════════════════════════════════════════════════════════════════════════════
1. THREE DEPLOYMENT MODES:
✓ Subdomain (production) → https://vendor.platform.com/shop
✓ Custom Domain (premium) → https://vendor-domain.com/shop
✓ Path-Based (development) → http://localhost:8000/vendor/v/shop
2. VENDOR DETECTION (4 Layers):
✓ URL detection from host/domain/path
✓ Middleware sets request.state.vendor
✓ Database queries filtered by vendor_id
✓ API authorization validates vendor ownership
3. CUSTOMER EXPERIENCE:
✓ No multi-tenant awareness needed
✓ Completely separate branded shops
✓ Each vendor has their own theme
✓ No data leakage between vendors
4. DEVELOPMENT WORKFLOW:
✓ Use path-based mode locally (/vendor/acme/...)
✓ Use subdomain mode in production
✓ Use custom domain for premium vendors
🔐 SECURITY
═════════════════════════════════════════════════════════════════════════════
✓ Multi-layer vendor isolation prevents cross-vendor access
✓ Database queries always filtered by vendor_id
✓ API endpoints validate vendor ownership
✓ Authentication cookies scoped to /shop path
✓ No way for customers to access other vendors' data
📞 NEED HELP?
═════════════════════════════════════════════════════════════════════════════
1. Check INDEX.md → "Most Common Questions" section
2. Check QUICK_REFERENCE.md → "Debugging Tips" section
3. Check COMPLETE_SUMMARY.txt → "Potential Issues" section
4. Review ARCHITECTURE_DIAGRAMS.txt → Visual reference
✨ NEXT STEPS
═════════════════════════════════════════════════════════════════════════════
1. Read INDEX.md (5 min) to get oriented
2. Choose a documentation file based on your needs
3. Learn the three deployment modes
4. Understand the middleware chain
5. Review the vendor isolation layers
6. Check the debugging tips
7. Start developing! 🚀
═══════════════════════════════════════════════════════════════════════════════
📊 DOCUMENTATION STATS:
Total Files: 5
Total Size: ~73 KB
Total Content: ~18,000 words
Topics Covered: 50+
Diagrams: 8+
Code Examples: 20+
Routes Documented: 25+
Generated: November 7, 2025
Version: Complete & Ready to Use ✅
═══════════════════════════════════════════════════════════════════════════════

View File

@@ -1,412 +0,0 @@
# Wizamart Multi-Tenant URL Routing - Documentation Index
**Generated:** November 7, 2025
**Project:** Wizamart Multi-Tenant E-Commerce Platform
**Topic:** Understanding how customers access vendor shops via URLs
---
## 📚 Documentation Files
### 1. **COMPLETE_SUMMARY.txt** ⭐ START HERE
**Best for:** Getting the complete picture in one file
Contains:
- TL;DR answer to the main question
- All three deployment modes explained
- Request processing flow
- Vendor isolation layers
- All shop routes
- Complete request lifecycle example
- Debugging tips & troubleshooting
- Security best practices
**Read this when:** You need everything in one place
---
### 2. **WIZAMART_MULTITENANT_URL_GUIDE.md**
**Best for:** Deep dive into multi-tenant architecture
Contains:
- Detailed explanation of all three deployment modes
- Subdomain mode with real-world example
- Custom domain mode with setup steps
- Path-based mode for development
- Complete route examples
- How vendor isolation works
- Request routing decision tree
- Theme integration
- Database perspective
- Potential issues and solutions
**Read this when:** You want comprehensive documentation
---
### 3. **QUICK_REFERENCE.md**
**Best for:** Quick lookups while coding
Contains:
- URL breakdown diagrams
- Request flow (in order)
- Detection priority (highest to lowest)
- All shop routes table
- API endpoints pattern
- Request state contents
- Vendor isolation multi-layer view
- Common scenarios
- Middleware files location
- Environment configuration
- Debugging tips
- Quick links to code files
**Read this when:** You need a specific answer quickly
---
### 4. **ARCHITECTURE_DIAGRAMS.txt**
**Best for:** Visual understanding of the system
Contains:
- Production deployment diagram
- Development deployment diagram
- Vendor detection decision tree
- Database schema (multi-tenant)
- Request context detection table
- Authorization layers
**Read this when:** You prefer visual representations
---
## 🎯 Quick Navigation by Use Case
### "I need to understand the basic URLs"
→ Read: **COMPLETE_SUMMARY.txt** (Top section)
→ Time: 5 minutes
### "I need to set up production deployment"
→ Read: **QUICK_REFERENCE.md** (Deployment Checklist section)
→ Then: **WIZAMART_MULTITENANT_URL_GUIDE.md** (Mode 1 & 2 sections)
→ Time: 15 minutes
### "I'm debugging a routing issue"
→ Read: **QUICK_REFERENCE.md** (Debugging Tips section)
→ Then: **COMPLETE_SUMMARY.txt** (Potential Issues section)
→ Time: 10 minutes
### "I need to understand vendor isolation"
→ Read: **COMPLETE_SUMMARY.txt** (Vendor Isolation section)
→ Then: **WIZAMART_MULTITENANT_URL_GUIDE.md** (How Vendor Isolation Works)
→ Time: 15 minutes
### "I want a complete technical overview"
→ Read: **WIZAMART_MULTITENANT_URL_GUIDE.md** (entire file)
→ Then: **ARCHITECTURE_DIAGRAMS.txt** (visual reference)
→ Time: 30 minutes
### "I need to learn the middleware flow"
→ Read: **ARCHITECTURE_DIAGRAMS.txt** (Production Deployment diagram)
→ Then: **COMPLETE_SUMMARY.txt** (Request Processing Flow)
→ Time: 10 minutes
---
## 📖 The Three Deployment Modes
### Mode 1: Subdomain (Production Standard)
```
URL: https://acme.wizamart.com/shop/products
Best for: Most vendors
SSL: Single wildcard certificate
DNS: Add subdomains as needed
```
📖 Details: WIZAMART_MULTITENANT_URL_GUIDE.md → "1. SUBDOMAIN MODE"
### Mode 2: Custom Domain (Production Premium)
```
URL: https://store.acme-corp.com/shop/products
Best for: Premium vendors
SSL: Vendor's own certificate
DNS: Vendor configures domain
```
📖 Details: WIZAMART_MULTITENANT_URL_GUIDE.md → "2. CUSTOM DOMAIN MODE"
### Mode 3: Path-Based (Development)
```
URL: http://localhost:8000/vendor/acme/shop/products
Best for: Development & testing
SSL: None needed
DNS: None needed
```
📖 Details: WIZAMART_MULTITENANT_URL_GUIDE.md → "3. PATH-BASED MODE"
---
## 🔄 Request Flow Overview
```
Customer Request
vendor_context_middleware ← Detects which vendor
context_middleware ← Determines context type (SHOP)
theme_context_middleware ← Loads vendor's theme
Route Matching ← /shop/products
Handler Execution ← Renders template
Template with Theme ← Uses vendor's colors, logo, fonts
HTML Response ← Fully branded shop
JavaScript API Call ← Fetches products
Customer sees Shop ← With vendor's theme
```
📖 Full details: COMPLETE_SUMMARY.txt → "REQUEST PROCESSING FLOW"
---
## 🛡️ Vendor Isolation (4 Layers)
```
Layer 1: URL Detection ← Identifies vendor from host/domain/path
Layer 2: Middleware ← Sets request.state.vendor
Layer 3: Database Queries ← WHERE vendor_id = ?
Layer 4: API Authorization ← Validates vendor_id
Result: No cross-vendor access possible
```
📖 Full details: COMPLETE_SUMMARY.txt → "VENDOR ISOLATION: 4 LAYERS"
---
## 🗂️ All Shop Routes
| Category | Routes |
|----------|--------|
| **Public** | `/shop/`, `/shop/products`, `/shop/categories/{slug}`, `/shop/search`, etc. |
| **Auth** | `/shop/account/register`, `/shop/account/login`, `/shop/account/forgot-password` |
| **Protected** | `/shop/account/dashboard`, `/shop/account/orders`, `/shop/account/profile`, etc. |
📖 Full table: QUICK_REFERENCE.md → "ALL SHOP ROUTES"
---
## 🔑 Key Files to Know
### Middleware Files
- `middleware/vendor_context.py` - Detects vendor from host/domain/path
- `middleware/context_middleware.py` - Determines request context type
- `middleware/theme_context.py` - Loads vendor-specific theme
- `middleware/logging_middleware.py` - Logs all requests
### Route Files
- `app/routes/shop_pages.py` - Customer shop pages
- `app/routes/vendor_pages.py` - Vendor dashboard pages
- `app/routes/admin_pages.py` - Admin pages
### Main Files
- `main.py` - FastAPI app setup & middleware chain
- `app/core/config.py` - Configuration including platform_domain
- `app/api/main.py` - API router configuration
---
## ⚡ Most Common Questions
### Q: What URL do I give to a customer?
**A:** Depends on deployment mode:
- Subdomain: `https://vendor-name.wizamart.com/shop/products`
- Custom Domain: `https://vendor-domain.com/shop/products`
- Development: `http://localhost:8000/vendor/vendor-code/shop/products`
📖 Reference: COMPLETE_SUMMARY.txt → "TL;DR"
---
### Q: How does the system know which vendor to show?
**A:** The `vendor_context_middleware` detects the vendor from:
1. Custom domain lookup (highest priority)
2. Subdomain extraction
3. URL path extraction (development)
📖 Reference: ARCHITECTURE_DIAGRAMS.txt → "VENDOR DETECTION DECISION TREE"
---
### Q: How is vendor data isolated?
**A:** Multi-layer isolation:
- Middleware sets request.state.vendor
- Database queries filtered by vendor_id
- API endpoints validate vendor ownership
- Cookies scoped to /shop path
📖 Reference: COMPLETE_SUMMARY.txt → "VENDOR ISOLATION: 4 LAYERS"
---
### Q: How are themes applied per vendor?
**A:** The `theme_context_middleware`:
1. Gets the vendor from middleware chain
2. Queries VendorTheme table by vendor_id
3. Sets request.state.theme with colors, fonts, logo
4. Template uses {{ request.state.theme.* }} to render
📖 Reference: WIZAMART_MULTITENANT_URL_GUIDE.md → "Theme Integration"
---
### Q: What happens if I visit vendor1.wizamart.com but the vendor doesn't exist?
**A:** The `vendor_context_middleware` returns None, and the request continues. The error handling depends on the route - shop routes will likely return a 404.
📖 Reference: QUICK_REFERENCE.md → "Debugging Tips"
---
### Q: Can customers access multiple vendors' shops?
**A:** Yes! They just visit different subdomains or custom domains:
- `https://acme.wizamart.com/shop/products` → ACME's store
- `https://techpro.wizamart.com/shop/products` → TechPro's store
Each is a completely separate session with different theme, products, auth token.
📖 Reference: COMPLETE_SUMMARY.txt → "KEY DIFFERENCES BETWEEN MODES"
---
## 🐛 Debugging Checklist
- [ ] Check `request.state.vendor.name` to see detected vendor
- [ ] Check `request.state.context_type` to see detected context
- [ ] Check server logs for "[OK] Vendor found via..." messages
- [ ] Test with curl: `curl -H "Host: acme.wizamart.com" http://localhost:8000/shop`
- [ ] Verify vendor exists in database
- [ ] Verify VendorTheme exists for vendor
- [ ] Check all database queries have `WHERE vendor_id = ?`
📖 Full debugging guide: QUICK_REFERENCE.md → "Debugging Tips"
---
## 🚀 Getting Started
### For Developers
1. Read: **COMPLETE_SUMMARY.txt** (5 min)
2. Read: **QUICK_REFERENCE.md** (5 min)
3. Look at: **ARCHITECTURE_DIAGRAMS.txt** (5 min)
4. Start coding!
### For DevOps/Deployment
1. Read: **WIZARD_MULTITENANT_URL_GUIDE.md** → Deployment sections
2. Read: **QUICK_REFERENCE.md** → Deployment Checklist
3. Follow checklist for your mode (Subdomain/Custom Domain)
### For Architects/Decision Makers
1. Read: **COMPLETE_SUMMARY.txt** → The Complete Picture
2. Read: **ARCHITECTURE_DIAGRAMS.txt** → All diagrams
3. Consider: Subdomain vs Custom Domain vs Path-Based
---
## 📝 Document Maintenance
This documentation covers:
- **FastAPI** - Backend framework
- **SQLAlchemy** - Database ORM
- **Jinja2** - Template engine
- **Alpine.js** - Frontend reactivity
- **Tailwind CSS** - Styling
Last Updated: November 7, 2025
Version: Current Development
Status: Complete & Tested
---
## 🔗 Related Code References
### Vendor Detection Logic
```python
# middleware/vendor_context.py:VendorContextManager.detect_vendor_context()
```
### Theme Loading Logic
```python
# middleware/theme_context.py:ThemeContextManager.get_vendor_theme()
```
### Context Detection Logic
```python
# middleware/context_middleware.py:ContextManager.detect_context()
```
### Shop Routes
```python
# app/routes/shop_pages.py
```
### Main App Setup
```python
# main.py - See middleware registration order
```
---
## 💡 Tips
- **Always test with multiple vendors** to ensure proper isolation
- **Check request.state.vendor** in templates to debug vendor detection
- **Monitor cross-vendor access attempts** for security
- **Use path-based routing** for local development
- **Use subdomain routing** for most production scenarios
- **Use custom domain** only when vendor pays for premium branding
---
## 📞 Quick Reference Card
```
DEPLOYMENT MODES:
Subdomain: https://vendor.platform.com/shop/products
Custom Domain: https://vendor-domain.com/shop/products
Path-Based: http://localhost:8000/vendor/vendor-code/shop/products
MIDDLEWARE CHAIN:
1. vendor_context_middleware → request.state.vendor
2. context_middleware → request.state.context_type
3. theme_context_middleware → request.state.theme
VENDOR ISOLATION:
URL Detection → Middleware → Database Queries → API Authorization
AUTHENTICATION:
JWT in header + customer_token cookie (path=/shop)
SHOP ROUTES:
Public: /shop/*, /shop/products, /shop/categories/{slug}
Auth: /shop/account/register, /shop/account/login
Protected: /shop/account/dashboard, /shop/account/orders
DATABASE:
Every table has vendor_id for multi-tenant isolation
```
---
**Need help?** Check the relevant documentation file above, or search for your specific question using the "Most Common Questions" section.

View File

@@ -1,357 +0,0 @@
# Wizamart Multi-Tenant URL Routing - QUICK REFERENCE
## TL;DR - Customer Shop URLs
### Production
```
Subdomain: https://acme.wizamart.com/shop/products
Custom Domain: https://store.acme-corp.com/shop/products
```
### Development
```
Path-Based: http://localhost:8000/vendor/acme/shop/products
```
---
## URL Breakdown
### Subdomain Mode
```
https://acme.wizamart.com/shop/products
↑ ↑ ↑ ↑
| | | └─ Route: /shop/products
| | └────── Context: /shop (shop frontend)
| └─────────────────────── Platform domain
└──────────────────────────── Vendor subdomain (from vendors.subdomain)
```
### Custom Domain Mode
```
https://store.acme-corp.com/shop/products
↑ ↑ ↑
| | └─ Route: /shop/products
| └────── Context: /shop (shop frontend)
└──────────────────────── Custom domain (from vendor_domains.domain)
```
### Path-Based Mode (Dev)
```
http://localhost:8000/vendor/acme/shop/products
↑ ↑ ↑ ↑ ↑
| | | | └─ Route: /shop/products
| | | └────── Context: /shop
| | └─────────── Vendor code: acme
| └────────────────── Development prefix: /vendor
└─────────────────────────────── Localhost
```
---
## Request Flow (In Order)
```
1. Request arrives
2. vendor_context_middleware
→ Detects vendor from host/path
→ Sets request.state.vendor = Vendor(...)
3. context_middleware
→ Determines RequestContext.SHOP
→ Sets request.state.context_type
4. theme_context_middleware
→ Loads vendor theme
→ Sets request.state.theme = {...}
5. FastAPI route matching
→ Matches /shop/products
6. Handler executes
→ Returns HTML template
7. Template renders
→ Uses vendor theme
→ Uses vendor name
8. JavaScript API call
→ fetch(/api/v1/public/vendors/{vendor_id}/products)
9. Response sent to browser
→ Fully branded shop
```
---
## Detection Priority (Highest to Lowest)
```
1⃣ Custom Domain (e.g., store.acme-corp.com)
└─ Queries: vendor_domains WHERE domain = 'store.acme-corp.com'
2⃣ Subdomain (e.g., acme.wizamart.com)
└─ Queries: vendors WHERE subdomain = 'acme'
3⃣ Path-Based (e.g., /vendor/acme/...)
└─ Queries: vendors WHERE subdomain = 'acme'
```
---
## All Shop Routes
| Path | Handler | Auth Required | Purpose |
|------|---------|---|---------|
| `/shop/` | `shop_products_page()` | ❌ | Homepage |
| `/shop/products` | `shop_products_page()` | ❌ | Product catalog |
| `/shop/products/{id}` | `shop_product_detail_page()` | ❌ | Product detail |
| `/shop/categories/{slug}` | `shop_category_page()` | ❌ | Category products |
| `/shop/cart` | `shop_cart_page()` | ❌ | Shopping cart |
| `/shop/checkout` | `shop_checkout_page()` | ❌ | Checkout |
| `/shop/search` | `shop_search_page()` | ❌ | Search results |
| `/shop/account/register` | `shop_register_page()` | ❌ | Customer registration |
| `/shop/account/login` | `shop_login_page()` | ❌ | Customer login |
| `/shop/account/dashboard` | `shop_account_dashboard_page()` | ✅ | Account dashboard |
| `/shop/account/orders` | `shop_orders_page()` | ✅ | Order history |
| `/shop/account/orders/{id}` | `shop_order_detail_page()` | ✅ | Order details |
| `/shop/account/profile` | `shop_profile_page()` | ✅ | Profile |
| `/shop/account/addresses` | `shop_addresses_page()` | ✅ | Address management |
| `/shop/account/wishlist` | `shop_wishlist_page()` | ✅ | Wishlist |
| `/shop/account/settings` | `shop_settings_page()` | ✅ | Account settings |
| `/shop/about` | `shop_about_page()` | ❌ | About us |
| `/shop/contact` | `shop_contact_page()` | ❌ | Contact |
| `/shop/privacy` | `shop_privacy_page()` | ❌ | Privacy policy |
| `/shop/terms` | `shop_terms_page()` | ❌ | Terms of service |
---
## API Endpoints (Vendor-Scoped)
All public API endpoints follow this pattern:
```
GET /api/v1/public/vendors/{vendor_id}/products
GET /api/v1/public/vendors/{vendor_id}/products/{product_id}
GET /api/v1/public/vendors/{vendor_id}/products/search
POST /api/v1/public/vendors/{vendor_id}/cart
```
These work from **ANY** domain/subdomain because `vendor_id` is explicit.
---
## Request State Contents
After middleware chain, handler receives:
```python
request.state.vendor = Vendor(
id=1,
name="ACME Store",
subdomain="acme",
is_active=True,
# ... other fields
)
request.state.vendor_context = {
"detection_method": "subdomain", # or "custom_domain" or "path"
"subdomain": "acme", # if subdomain/path mode
"domain": "store.acme-corp.com", # if custom_domain mode
"host": "acme.wizamart.com" # original host
}
request.state.context_type = RequestContext.SHOP # or ADMIN, VENDOR_DASHBOARD, etc.
request.state.theme = {
"theme_name": "modern",
"colors": {
"primary": "#FF6B6B",
"secondary": "#FF8787",
# ...
},
"branding": {
"logo": "acme-logo.png",
"favicon": "acme-favicon.ico",
# ...
},
"fonts": {
"heading": "Poppins, sans-serif",
"body": "Inter, sans-serif"
}
}
request.state.clean_path = "/shop/products" # For path-based dev mode
```
---
## Vendor Isolation (Multi-Layer)
```
Layer 1: URL Detection
├─ Subdomain: vendor from host
├─ Custom domain: vendor from VendorDomain table
└─ Path: vendor from URL path
Layer 2: Middleware
└─ request.state.vendor set to correct Vendor object
Layer 3: Database Queries
└─ WHERE vendor_id = ?
(EVERY query includes vendor filter)
Layer 4: API Authorization
└─ Verify vendor matches request context
(No cross-vendor access possible)
```
---
## Common Scenarios
### Scenario 1: Customer on ACME's subdomain
```
URL: https://acme.wizamart.com/shop/products
Vendor detected: Vendor(id=1, subdomain="acme")
Database query: SELECT products WHERE vendor_id = 1 AND is_active = true
Result: ACME's products
```
### Scenario 2: Customer on TechPro's custom domain
```
URL: https://shop.techpro.io/shop/products
Vendor detected: Via VendorDomain(domain="shop.techpro.io", vendor_id=2)
Database query: SELECT products WHERE vendor_id = 2 AND is_active = true
Result: TechPro's products
```
### Scenario 3: Developer testing locally
```
URL: http://localhost:8000/vendor/acme/shop/products
Vendor detected: Vendor(id=1, subdomain="acme") via /vendor/acme/
Database query: SELECT products WHERE vendor_id = 1
Result: ACME's products
```
### Scenario 4: Cross-vendor attack attempt
```
Attack: Customer on acme.wizamart.com tries to access TechPro's API
URL: https://acme.wizamart.com/api/v1/public/vendors/2/products
Backend check: Is vendor_id in request (2) same as request.state.vendor.id (1)?
Result: ❌ UnauthorizedShopAccessException
```
---
## Middleware Files
```
middleware/
├─ vendor_context.py ← Detects vendor from host/path
├─ theme_context.py ← Loads vendor theme
├─ context_middleware.py ← Determines request context type
└─ logging_middleware.py ← Logs all requests
```
## Route Files
```
app/routes/
├─ shop_pages.py ← Customer shop pages
├─ vendor_pages.py ← Vendor dashboard pages
└─ admin_pages.py ← Admin pages
```
---
## Environment Configuration
```python
# app/core/config.py
platform_domain = "wizamart.com" # Platform domain for subdomains
# Allows all *.wizamart.com subdomains
allowed_hosts = ["*"]
```
---
## Development vs Production
### Development
```
HTTP (not HTTPS - no SSL)
localhost:8000
Path-based routing: /vendor/vendor_code/...
No DNS setup needed
All vendors share same localhost
```
### Production
```
HTTPS (required - SSL certs)
Subdomain: *.wizamart.com (wildcard SSL cert)
Custom: vendor.domain.com (vendor's SSL cert)
DNS setup needed for subdomains/custom domains
Vendors have separate endpoints
```
---
## Debugging Tips
### Check which vendor was detected
```python
# In template or handler:
{{ request.state.vendor.name }} # "ACME Store"
{{ request.state.vendor.subdomain }} # "acme"
{{ request.state.context_type }} # "shop"
```
### Check which theme was loaded
```python
# In template:
{{ request.state.theme.colors.primary }} # "#FF6B6B"
{{ request.state.theme.branding.logo }} # "acme-logo.png"
```
### Check vendor detection logs
```bash
# In server logs, look for:
[OK] Vendor found via subdomain: acme -> ACME Store
[WARNING] Vendor not found for context: {...}
```
### Test with curl
```bash
# Subdomain mode
curl -H "Host: acme.wizamart.com" http://localhost:8000/shop/products
# Path-based mode
curl http://localhost:8000/vendor/acme/shop/products
```
---
## Quick Links in Code
- Vendor detection: `middleware/vendor_context.py:VendorContextManager.detect_vendor_context()`
- Theme loading: `middleware/theme_context.py:ThemeContextManager.get_vendor_theme()`
- Context detection: `middleware/context_middleware.py:ContextManager.detect_context()`
- Shop routes: `app/routes/shop_pages.py`
- Main app setup: `main.py`
---
## Reference
- Platform: Wizamart
- Mode: Multi-tenant SaaS e-commerce
- Framework: FastAPI + SQLAlchemy + Jinja2
- Frontend: Alpine.js + Tailwind CSS
- Auth: JWT + Vendor-scoped cookies
Generated: November 7, 2025

View File

@@ -1,458 +0,0 @@
# VENDOR THEME EDITOR - COMPLETE IMPLEMENTATION GUIDE
Following Your Frontend Architecture
## 📋 Overview
This implementation follows your **exact Alpine.js architecture pattern** with:
- ✅ Proper logging setup
-`...data()` inheritance
- ✅ Initialization guard pattern
- ✅ Lowercase `apiClient` usage
- ✅ Page-specific logger (`themeLog`)
-`currentPage` identifier
- ✅ Performance tracking
- ✅ Proper error handling
---
## 📦 Files to Install
### 1. JavaScript Component (Alpine.js)
**File:** `vendor-theme-alpine.js`
**Install to:** `static/admin/js/vendor-theme.js`
```bash
cp vendor-theme-alpine.js static/admin/js/vendor-theme.js
```
**Key Features:**
- ✅ Follows `dashboard.js` pattern exactly
- ✅ Uses `adminVendorTheme()` function name
- ✅ Inherits base with `...data()`
- ✅ Sets `currentPage: 'vendor-theme'`
- ✅ Has initialization guard
- ✅ Uses lowercase `apiClient`
- ✅ Has `themeLog` logger
- ✅ Performance tracking with `Date.now()`
---
### 2. API Endpoints (Backend)
**File:** `vendor_themes_api.py`
**Install to:** `app/api/v1/admin/vendor_themes.py`
```bash
cp vendor_themes_api.py app/api/v1/admin/vendor_themes.py
```
**Endpoints:**
```
GET /api/v1/admin/vendor-themes/presets
GET /api/v1/admin/vendor-themes/{vendor_code}
PUT /api/v1/admin/vendor-themes/{vendor_code}
POST /api/v1/admin/vendor-themes/{vendor_code}/preset/{preset_name}
DELETE /api/v1/admin/vendor-themes/{vendor_code}
```
---
### 3. Pydantic Schemas
**File:** `vendor_theme_schemas.py`
**Install to:** `models/schema/vendor_theme.py`
```bash
cp vendor_theme_schemas.py models/schema/vendor_theme.py
```
---
### 4. HTML Template
**File:** `vendor-theme.html`
**Install to:** `app/templates/admin/vendor-theme.html`
```bash
cp vendor-theme.html app/templates/admin/vendor-theme.html
```
**Key Features:**
- ✅ Extends `admin/base.html`
- ✅ Uses `{% block alpine_data %}adminVendorTheme(){% endblock %}`
- ✅ Loads script in `{% block extra_scripts %}`
- ✅ 7 preset buttons
- ✅ 6 color pickers
- ✅ Font and layout selectors
- ✅ Live preview panel
---
### 5. Frontend Router Update
**File:** `pages-updated.py`
**Install to:** `app/api/v1/admin/pages.py`
```bash
cp pages-updated.py app/api/v1/admin/pages.py
```
**Change:**
- Added route: `GET /vendors/{vendor_code}/theme`
- Returns: `admin/vendor-theme.html`
---
### 6. API Router Registration
**File:** `__init__-updated.py`
**Install to:** `app/api/v1/admin/__init__.py`
```bash
cp __init__-updated.py app/api/v1/admin/__init__.py
```
**Changes:**
```python
# Added import
from . import vendor_themes
# Added router registration
router.include_router(vendor_themes.router, tags=["admin-vendor-themes"])
```
---
### 7. Theme Presets (if not already installed)
**File:** `theme_presets.py`
**Install to:** `app/core/theme_presets.py`
```bash
cp theme_presets.py app/core/theme_presets.py
```
---
## 🔧 Installation Steps
### Step 1: Copy All Files
```bash
# 1. JavaScript (Frontend)
cp vendor-theme-alpine.js static/admin/js/vendor-theme.js
# 2. API Endpoints (Backend)
cp vendor_themes_api.py app/api/v1/admin/vendor_themes.py
# 3. Pydantic Schemas
cp vendor_theme_schemas.py models/schema/vendor_theme.py
# 4. HTML Template
cp vendor-theme.html app/templates/admin/vendor-theme.html
# 5. Theme Presets (if not done)
cp theme_presets.py app/core/theme_presets.py
# 6. Update Frontend Router
cp pages-updated.py app/api/v1/admin/pages.py
# 7. Update API Router
cp __init__-updated.py app/api/v1/admin/__init__.py
```
---
### Step 2: Verify Database
Make sure the `vendor_themes` table exists:
```bash
# Check migrations
alembic history
# Run migration if needed
alembic upgrade head
```
---
### Step 3: Restart Server
```bash
# Stop server
pkill -f uvicorn
# Start server
python -m uvicorn app.main:app --reload
```
---
## 🧪 Testing
### 1. Check JavaScript Loading
Open browser console and look for:
```
[THEME INFO] Vendor theme editor module loaded
```
---
### 2. Test Page Load
Navigate to:
```
http://localhost:8000/admin/vendors/VENDOR001/theme
```
Expected console output:
```
[THEME INFO] === VENDOR THEME EDITOR INITIALIZING ===
[THEME INFO] Vendor code: VENDOR001
[THEME INFO] Loading vendor data...
[THEME INFO] Vendor loaded in 45ms: Vendor Name
[THEME INFO] Loading theme...
[THEME INFO] Theme loaded in 23ms: default
[THEME INFO] Loading presets...
[THEME INFO] 7 presets loaded in 12ms
[THEME INFO] === THEME EDITOR INITIALIZATION COMPLETE (80ms) ===
```
---
### 3. Test Preset Application
Click "Modern" preset button.
Expected console output:
```
[THEME INFO] Applying preset: modern
[THEME INFO] Preset applied in 56ms
```
Expected UI: Colors and fonts update instantly
---
### 4. Test Color Changes
1. Click primary color picker
2. Choose a new color
3. Preview should update immediately
---
### 5. Test Save
1. Click "Save Theme" button
2. Expected toast: "Theme saved successfully"
3. Expected console:
```
[THEME INFO] Saving theme: {theme_name: 'modern', ...}
[THEME INFO] Theme saved in 34ms
```
---
### 6. Test API Endpoints
```bash
# Get presets
curl http://localhost:8000/api/v1/admin/vendor-themes/presets \
-H "Authorization: Bearer YOUR_TOKEN"
# Get vendor theme
curl http://localhost:8000/api/v1/admin/vendor-themes/VENDOR001 \
-H "Authorization: Bearer YOUR_TOKEN"
# Apply preset
curl -X POST \
http://localhost:8000/api/v1/admin/vendor-themes/VENDOR001/preset/modern \
-H "Authorization: Bearer YOUR_TOKEN"
# Save theme
curl -X PUT \
http://localhost:8000/api/v1/admin/vendor-themes/VENDOR001 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"colors": {
"primary": "#ff0000"
}
}'
```
---
## 🎯 Architecture Compliance Checklist
### JavaScript File ✅
- [x] Logging setup (`themeLog`)
- [x] Function name: `adminVendorTheme()`
- [x] `...data()` at start
- [x] `currentPage: 'vendor-theme'`
- [x] Initialization guard (`window._vendorThemeInitialized`)
- [x] Uses lowercase `apiClient`
- [x] Uses page-specific logger (`themeLog`)
- [x] Performance tracking (`Date.now()`)
- [x] Module loaded log at end
---
### HTML Template ✅
- [x] Extends `admin/base.html`
- [x] `alpine_data` block uses `adminVendorTheme()`
- [x] `x-show` for loading states
- [x] `x-text` for reactive data
- [x] Loads JS in `extra_scripts` block
---
### API Routes ✅
- [x] RESTful endpoint structure
- [x] Proper error handling
- [x] Admin authentication required
- [x] Pydantic validation
- [x] Logging
---
## 📊 Data Flow
```
1. User visits /admin/vendors/VENDOR001/theme
2. pages.py returns vendor-theme.html
3. Template loads vendor-theme.js
4. Alpine.js calls adminVendorTheme()
5. Component spreads ...data() (gets base UI state)
6. Component adds page-specific state
7. init() runs (with guard)
8. Loads vendor data via apiClient.get()
9. Loads theme data via apiClient.get()
10. Loads presets via apiClient.get()
11. UI updates reactively
12. User clicks preset button
13. applyPreset() sends apiClient.post()
14. API applies preset and saves to DB
15. Response updates themeData
16. UI updates (colors, fonts, preview)
```
---
## 🐛 Troubleshooting
### "adminVendorTheme is not defined"
**Problem:** JavaScript file not loaded or function name mismatch
**Fix:**
1. Check template: `{% block alpine_data %}adminVendorTheme(){% endblock %}`
2. Check JS file has: `function adminVendorTheme() {`
3. Check JS file is loaded in `extra_scripts` block
---
### "apiClient is not defined"
**Problem:** `api-client.js` not loaded before your script
**Fix:** Check `base.html` loads scripts in this order:
1. `log-config.js`
2. `icons.js`
3. `init-alpine.js`
4. `utils.js`
5. `api-client.js`
6. Alpine.js CDN
7. Your page script (vendor-theme.js)
---
### Init runs multiple times
**Problem:** Guard not working
**Expected:** You should see this warning in console:
```
⚠️ [THEME WARN] Theme editor already initialized, skipping...
```
**If not appearing:** Check guard is in `init()`:
```javascript
if (window._vendorThemeInitialized) {
themeLog.warn('Theme editor already initialized, skipping...');
return;
}
window._vendorThemeInitialized = true;
```
---
### No console logs
**Problem:** Log level too low
**Fix:** Set `THEME_LOG_LEVEL = 4` at top of vendor-theme.js for all logs
---
## ✅ Final Checklist
- [ ] All 7 files copied
- [ ] API router registered in `__init__.py`
- [ ] Frontend route added in `pages.py`
- [ ] Database has `vendor_themes` table
- [ ] Server restarted
- [ ] Can access `/admin/vendors/VENDOR001/theme`
- [ ] Console shows module loaded log
- [ ] Console shows initialization logs
- [ ] Presets load and work
- [ ] Color pickers work
- [ ] Save button works
- [ ] No console errors
---
## 🎉 Success Indicators
When everything is working:
1. **Console logs:**
```
[THEME INFO] Vendor theme editor module loaded
[THEME INFO] === VENDOR THEME EDITOR INITIALIZING ===
[THEME INFO] === THEME EDITOR INITIALIZATION COMPLETE (XXms) ===
```
2. **No errors** in browser console
3. **Live preview** updates when you change colors
4. **Preset buttons** change theme instantly
5. **Save button** shows success toast
6. **Changes persist** after page refresh
---
## 📚 Related Documentation
- Frontend Architecture: `FRONTEND_ARCHITECTURE_OVERVIEW.txt`
- Alpine.js Template: `FRONTEND_ALPINE_PAGE_TEMPLATE.md`
- Your working example: `static/admin/js/dashboard.js`
---
**Your theme editor now follows your exact frontend architecture! 🎨**

View File

@@ -1,509 +0,0 @@
# Theme System Integration Guide for Your Existing Architecture
## 🎯 Overview
This guide shows how to integrate the multi-theme system into your **existing** FastAPI + Alpine.js + Tailwind CSS architecture.
**Key Point:** You already have 80% of what you need! Your `theme_config` JSON field in the Vendor model is the foundation.
## ✅ What You Already Have
1. **Vendor model** with `theme_config` JSON field ✅
2. **Alpine.js** frontend pattern established ✅
3. **Tailwind CSS** for styling ✅
4. **Admin pages** with Jinja2 templates ✅
5. **Vendor context middleware**
## 🚀 Integration Steps
### Step 1: Update Vendor Model (5 minutes)
Your current model already has `theme_config`, so just add helper methods:
```python
# models/database/vendor.py
class Vendor(Base, TimestampMixin):
# ... existing fields ...
theme_config = Column(JSON, default=dict) # ✅ You already have this!
# ADD THIS PROPERTY:
@property
def theme(self):
"""
Get theme configuration for this vendor.
Returns dict with theme configuration.
"""
if self.theme_config:
return self._normalize_theme_config(self.theme_config)
return self._get_default_theme()
def _normalize_theme_config(self, config: dict) -> dict:
"""Ensure theme_config has all required fields"""
return {
"colors": config.get("colors", {
"primary": "#6366f1",
"secondary": "#8b5cf6",
"accent": "#ec4899"
}),
"fonts": config.get("fonts", {
"heading": "Inter, sans-serif",
"body": "Inter, sans-serif"
}),
"layout": config.get("layout", {
"style": "grid",
"header": "fixed"
}),
"branding": config.get("branding", {
"logo": None,
"favicon": None
}),
"custom_css": config.get("custom_css", None),
"css_variables": self._generate_css_variables(config)
}
def _generate_css_variables(self, config: dict) -> dict:
"""Generate CSS custom properties from theme"""
colors = config.get("colors", {})
fonts = config.get("fonts", {})
return {
"--color-primary": colors.get("primary", "#6366f1"),
"--color-secondary": colors.get("secondary", "#8b5cf6"),
"--color-accent": colors.get("accent", "#ec4899"),
"--font-heading": fonts.get("heading", "Inter, sans-serif"),
"--font-body": fonts.get("body", "Inter, sans-serif"),
}
def _get_default_theme(self) -> dict:
"""Default theme if none configured"""
return {
"colors": {"primary": "#6366f1", "secondary": "#8b5cf6", "accent": "#ec4899"},
"fonts": {"heading": "Inter, sans-serif", "body": "Inter, sans-serif"},
"layout": {"style": "grid", "header": "fixed"},
"branding": {"logo": None, "favicon": None},
"custom_css": None,
"css_variables": {
"--color-primary": "#6366f1",
"--color-secondary": "#8b5cf6",
"--color-accent": "#ec4899",
"--font-heading": "Inter, sans-serif",
"--font-body": "Inter, sans-serif",
}
}
```
**That's it for the model!** No new tables needed if you use the existing `theme_config` JSON field.
### Step 2: Add Theme Route to Admin Pages (2 minutes)
Add to `app/api/v1/admin/pages.py`:
```python
@router.get("/vendors/{vendor_code}/theme", response_class=HTMLResponse, include_in_schema=False)
async def admin_vendor_theme_page(
request: Request,
vendor_code: str = Path(..., description="Vendor code"),
current_user: User = Depends(get_current_admin_user),
db: Session = Depends(get_db)
):
"""
Render vendor theme customization page.
"""
return templates.TemplateResponse(
"admin/vendor-theme.html",
{
"request": request,
"user": current_user,
"vendor_code": vendor_code,
}
)
```
### Step 3: Create Theme Management Page (5 minutes)
1. Copy the HTML template from `/mnt/user-data/outputs/vendor-theme-page.html`
2. Save as `app/templates/admin/vendor-theme.html`
3. Copy the JS component from `/mnt/user-data/outputs/vendor-theme.js`
4. Save as `static/admin/js/vendor-theme.js`
### Step 4: Update Vendor Detail Page to Link to Theme (2 minutes)
In your `app/templates/admin/vendor-detail.html`, add a "Customize Theme" button:
```html
<!-- In your vendor details page -->
<div class="flex space-x-2">
<a :href="`/admin/vendors/${vendor?.vendor_code}/edit`"
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
Edit Vendor
</a>
<!-- ADD THIS BUTTON -->
<a :href="`/admin/vendors/${vendor?.vendor_code}/theme`"
class="px-4 py-2 text-sm font-medium text-purple-700 bg-white border border-purple-600 rounded-lg hover:bg-purple-50">
Customize Theme
</a>
</div>
```
### Step 5: Update Shop Template to Use Theme (10 minutes)
Create `app/templates/shop/base.html` (if you don't have it yet):
```html
<!DOCTYPE html>
<html lang="en" x-data="shopLayoutData()" x-bind:class="{ 'dark': dark }">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ vendor.name }}{% endblock %}</title>
<!-- ✅ CRITICAL: Inject theme CSS variables -->
<style id="vendor-theme-variables">
:root {
{% for key, value in theme.css_variables.items() %}
{{ key }}: {{ value }};
{% endfor %}
}
/* Custom CSS from vendor */
{% if theme.custom_css %}
{{ theme.custom_css | safe }}
{% endif %}
</style>
<!-- Tailwind CSS -->
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body class="bg-gray-50">
<!-- Header -->
<header class="bg-white shadow-sm">
<div class="max-w-7xl mx-auto px-4 py-4">
<div class="flex items-center justify-between">
<!-- Logo -->
<div class="flex items-center">
{% if theme.branding.logo %}
<img src="{{ theme.branding.logo }}"
alt="{{ vendor.name }}"
class="h-8">
{% else %}
<h1 class="text-xl font-bold"
style="color: var(--color-primary)">
{{ vendor.name }}
</h1>
{% endif %}
</div>
<!-- Navigation -->
<nav class="flex space-x-6">
<a href="/" class="hover:text-primary">Home</a>
<a href="/products" class="hover:text-primary">Products</a>
<a href="/about" class="hover:text-primary">About</a>
</nav>
</div>
</div>
</header>
<!-- Main Content -->
<main>
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="bg-gray-100 mt-12 py-8">
<div class="max-w-7xl mx-auto px-4 text-center text-gray-600">
<p>&copy; {{ now().year }} {{ vendor.name }}</p>
</div>
</footer>
</body>
</html>
```
### Step 6: Update Shop Routes to Pass Theme (5 minutes)
In your shop route handlers (e.g., `app/api/v1/public/vendors/pages.py`):
```python
@router.get("/")
async def shop_home(request: Request, db: Session = Depends(get_db)):
vendor = request.state.vendor # From vendor context middleware
# Get theme from vendor
theme = vendor.theme # Uses the property we added
return templates.TemplateResponse("shop/home.html", {
"request": request,
"vendor": vendor,
"theme": theme, # ✅ Pass theme to template
})
```
## 📊 Using Tailwind with Theme Variables
The magic is in CSS variables! Tailwind can use your theme colors:
```html
<!-- In your shop templates -->
<!-- Primary color button -->
<button class="px-4 py-2 rounded-lg"
style="background-color: var(--color-primary); color: white;">
Shop Now
</button>
<!-- Or use Tailwind utilities with inline styles -->
<div class="text-2xl font-bold"
style="color: var(--color-primary); font-family: var(--font-heading)">
Welcome to {{ vendor.name }}
</div>
<!-- Product card with theme colors -->
<div class="p-4 bg-white rounded-lg border"
style="border-color: var(--color-primary)">
<h3 class="text-lg font-semibold"
style="color: var(--color-primary)">
Product Name
</h3>
</div>
```
## 🎨 How It All Works Together
### 1. Admin Customizes Theme
```
Admin → /admin/vendors/TECHSTORE/theme
Sees theme editor (colors, fonts, layout)
Clicks "Save Theme"
PUT /api/v1/admin/vendors/TECHSTORE
Updates vendor.theme_config JSON:
{
"colors": {
"primary": "#2563eb", // Blue
"secondary": "#0ea5e9"
},
"fonts": {
"heading": "Roboto, sans-serif"
}
}
```
### 2. Customer Visits Shop
```
Customer → techstore.platform.com
Vendor middleware identifies Vendor = TECHSTORE
Shop route loads:
- vendor object
- theme = vendor.theme (property we added)
Template renders with:
:root {
--color-primary: #2563eb;
--color-secondary: #0ea5e9;
--font-heading: Roboto, sans-serif;
}
Customer sees blue-themed shop with Roboto headings!
```
## 🔧 API Endpoints Needed
Your existing vendor update endpoint already works! Just update `theme_config`:
```python
# app/api/v1/admin/vendors.py
@router.put("/vendors/{vendor_code}")
async def update_vendor(
vendor_code: str,
vendor_data: VendorUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin_user)
):
vendor = db.query(Vendor).filter(
Vendor.vendor_code == vendor_code
).first()
if not vendor:
raise HTTPException(404, "Vendor not found")
# Update fields
if vendor_data.theme_config is not None:
vendor.theme_config = vendor_data.theme_config # ✅ This already works!
# ... other updates ...
db.commit()
db.refresh(vendor)
return vendor
```
**That's it!** Your existing update endpoint handles theme updates.
## 🎯 Quick Test
### 1. Test Theme Editor
```bash
# Start your app
uvicorn main:app --reload
# Visit admin
http://localhost:8000/admin/vendors/YOUR_VENDOR_CODE/theme
# Change colors and save
# Check database:
SELECT theme_config FROM vendors WHERE vendor_code = 'YOUR_VENDOR_CODE';
# Should see:
# {"colors": {"primary": "#your-color", ...}}
```
### 2. Test Shop Rendering
```bash
# Visit vendor shop
http://vendor1.localhost:8000/
# Inspect page source
# Should see:
# <style id="vendor-theme-variables">
# :root {
# --color-primary: #your-color;
# }
# </style>
```
## 📦 Files to Create/Update
### Create New Files:
1. `app/templates/admin/vendor-theme.html` ← From `/mnt/user-data/outputs/vendor-theme-page.html`
2. `static/admin/js/vendor-theme.js` ← From `/mnt/user-data/outputs/vendor-theme.js`
3. `app/templates/shop/base.html` ← Base shop template with theme support
### Update Existing Files:
1. `models/database/vendor.py` ← Add `theme` property and helper methods
2. `app/api/v1/admin/pages.py` ← Add theme route
3. `models/schema/vendor.py` ← Already has `theme_config` in VendorUpdate ✅
## 🎨 Example: Different Themes
### Vendor 1: Tech Store (Blue Theme)
```python
{
"colors": {
"primary": "#2563eb",
"secondary": "#0ea5e9",
"accent": "#f59e0b"
},
"fonts": {
"heading": "Roboto, sans-serif",
"body": "Open Sans, sans-serif"
},
"layout": {
"style": "grid"
}
}
```
### Vendor 2: Fashion Boutique (Pink Theme)
```python
{
"colors": {
"primary": "#ec4899",
"secondary": "#f472b6",
"accent": "#fbbf24"
},
"fonts": {
"heading": "Playfair Display, serif",
"body": "Lato, sans-serif"
},
"layout": {
"style": "masonry"
}
}
```
## ✅ Checklist
- [ ] Add `theme` property to Vendor model
- [ ] Add theme route to `pages.py`
- [ ] Create `vendor-theme.html` template
- [ ] Create `vendor-theme.js` Alpine component
- [ ] Create shop `base.html` with theme injection
- [ ] Update shop routes to pass `theme`
- [ ] Add "Customize Theme" button to vendor detail page
- [ ] Test theme editor in admin
- [ ] Test theme rendering on shop
- [ ] Verify CSS variables work with Tailwind
## 🚀 Benefits
### For You:
- ✅ Uses existing `theme_config` field (no migration!)
- ✅ Works with current Alpine.js pattern
- ✅ Compatible with Tailwind CSS
- ✅ Follows your established conventions
### For Vendors:
- ✅ Easy theme customization
- ✅ Live preview
- ✅ Preset templates
- ✅ Custom branding
## 💡 Advanced: Tailwind Custom Configuration
If you want Tailwind to use theme variables natively:
```javascript
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: 'var(--color-primary)',
secondary: 'var(--color-secondary)',
accent: 'var(--color-accent)',
},
fontFamily: {
heading: 'var(--font-heading)',
body: 'var(--font-body)',
}
}
}
}
```
Then you can use:
```html
<button class="bg-primary text-white">Shop Now</button>
<h1 class="font-heading text-primary">Welcome</h1>
```
## 🎉 Summary
**Your architecture is perfect for this!**
1. You already have `theme_config` JSON field ✅
2. Just add a `theme` property to access it ✅
3. Create admin page to edit it ✅
4. Inject CSS variables in shop templates ✅
5. Use variables with Tailwind/inline styles ✅
**Total implementation time: ~30 minutes**
Each vendor gets unique theming without any database migrations! 🚀

View File

@@ -1,536 +0,0 @@
# VENDOR THEME BACKEND - PROPER ARCHITECTURE IMPLEMENTATION
## Overview
This implementation follows your **complete backend architecture** with:
-**Separation of Concerns** - Service layer handles business logic
-**Exception Management** - Custom exceptions for all error cases
-**Proper Layering** - Database → Service → API → Frontend
-**Naming Conventions** - Follows your established patterns
-**No Business Logic in Endpoints** - All logic in service layer
---
## 📦 File Structure
```
app/
├── exceptions/
│ ├── __init__.py # ← UPDATE (add vendor_theme imports)
│ └── vendor_theme.py # ← NEW (custom exceptions)
├── services/
│ └── vendor_theme_service.py # ← NEW (business logic)
└── api/v1/admin/
├── __init__.py # ← UPDATE (register router)
├── pages.py # ← UPDATE (add theme route)
└── vendor_themes.py # ← NEW (endpoints only)
models/
└── schema/
└── vendor_theme.py # ← Already created (Pydantic)
static/admin/js/
└── vendor-theme.js # ← Already created (Alpine.js)
app/templates/admin/
└── vendor-theme.html # ← Already created (HTML)
```
---
## 🏗️ Architecture Layers
### Layer 1: Exceptions (Error Handling)
**File:** `app/exceptions/vendor_theme.py`
```python
# Custom exceptions for domain-specific errors
- VendorThemeNotFoundException
- ThemePresetNotFoundException
- InvalidThemeDataException
- ThemeValidationException
- InvalidColorFormatException
- InvalidFontFamilyException
- ThemeOperationException
```
**Key Points:**
- ✅ Extends base exceptions (`ResourceNotFoundException`, etc.)
- ✅ Provides specific error codes
- ✅ Includes detailed error information
- ✅ No HTTP status codes (that's endpoint layer)
---
### Layer 2: Service (Business Logic)
**File:** `app/services/vendor_theme_service.py`
**Responsibilities:**
- ✅ Get/Create/Update/Delete themes
- ✅ Apply presets
- ✅ Validate theme data
- ✅ Database operations
- ✅ Raise custom exceptions
**Does NOT:**
- ❌ Handle HTTP requests/responses
- ❌ Raise HTTPException
- ❌ Know about FastAPI
**Example Service Method:**
```python
def update_theme(
self,
db: Session,
vendor_code: str,
theme_data: VendorThemeUpdate
) -> VendorTheme:
"""Update theme - returns VendorTheme or raises exception."""
try:
vendor = self._get_vendor_by_code(db, vendor_code)
theme = # ... get or create theme
# Validate
self._validate_theme_data(theme_data)
# Update
self._apply_theme_updates(theme, theme_data)
db.commit()
return theme
except CustomException:
raise # Re-raise custom exceptions
except Exception as e:
db.rollback()
raise ThemeOperationException(...) # Wrap generic errors
```
---
### Layer 3: API Endpoints (HTTP Interface)
**File:** `app/api/v1/admin/vendor_themes.py`
**Responsibilities:**
- ✅ Handle HTTP requests
- ✅ Call service methods
- ✅ Convert exceptions to HTTP responses
- ✅ Return JSON responses
**Does NOT:**
- ❌ Contain business logic
- ❌ Validate data (service does this)
- ❌ Access database directly
**Example Endpoint:**
```python
@router.put("/{vendor_code}")
async def update_vendor_theme(
vendor_code: str,
theme_data: VendorThemeUpdate,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user)
):
"""Endpoint - just calls service and handles errors."""
try:
# Call service (all logic there)
theme = vendor_theme_service.update_theme(db, vendor_code, theme_data)
# Return response
return theme.to_dict()
except VendorNotFoundException:
raise HTTPException(status_code=404, detail="...")
except ThemeValidationException as e:
raise HTTPException(status_code=422, detail=e.message)
except Exception as e:
logger.error(...)
raise HTTPException(status_code=500, detail="...")
```
---
## 📥 Installation
### Step 1: Install Exception Layer
```bash
# 1. Copy exception file
cp vendor_theme_exceptions.py app/exceptions/vendor_theme.py
# 2. Update exceptions __init__.py
cp exceptions__init__-updated.py app/exceptions/__init__.py
```
**Verify:**
```python
from app.exceptions import VendorThemeNotFoundException # Should work
```
---
### Step 2: Install Service Layer
```bash
# Copy service file
cp vendor_theme_service.py app/services/vendor_theme_service.py
```
**Verify:**
```python
from app.services.vendor_theme_service import vendor_theme_service # Should work
```
---
### Step 3: Install API Layer
```bash
# Copy API endpoints
cp vendor_themes_endpoints.py app/api/v1/admin/vendor_themes.py
```
**Verify:**
```python
from app.api.v1.admin import vendor_themes # Should work
```
---
### Step 4: Register Router
```bash
# Update API router
cp __init__-updated.py app/api/v1/admin/__init__.py
```
**Changes:**
```python
# Added:
from . import vendor_themes
router.include_router(vendor_themes.router, tags=["admin-vendor-themes"])
```
---
### Step 5: Update Frontend Router
```bash
# Update pages router
cp pages-updated.py app/api/v1/admin/pages.py
```
**Added Route:**
```python
@router.get("/vendors/{vendor_code}/theme")
async def admin_vendor_theme_page(...):
return templates.TemplateResponse("admin/vendor-theme.html", ...)
```
---
### Step 6: Copy Frontend Files (Already Done)
```bash
# JavaScript
cp vendor-theme-alpine.js static/admin/js/vendor-theme.js
# HTML Template
cp vendor-theme.html app/templates/admin/vendor-theme.html
# Pydantic Schemas
cp vendor_theme_schemas.py models/schema/vendor_theme.py
# Theme Presets
cp theme_presets.py app/core/theme_presets.py
```
---
## 🔄 Data Flow
### Complete Request Flow
```
1. User clicks "Save Theme" button
2. JavaScript (vendor-theme.js)
- apiClient.put('/api/v1/admin/vendor-themes/VENDOR001', data)
3. API Endpoint (vendor_themes.py)
- Receives HTTP PUT request
- Validates admin authentication
- Calls service layer
4. Service Layer (vendor_theme_service.py)
- Validates theme data
- Gets vendor from database
- Creates/updates VendorTheme
- Commits transaction
- Returns VendorTheme object
5. API Endpoint
- Converts VendorTheme to dict
- Returns JSON response
6. JavaScript
- Receives response
- Shows success toast
- Updates UI
```
### Error Flow
```
1. Service detects invalid color
2. Service raises InvalidColorFormatException
3. API endpoint catches exception
4. API converts to HTTPException(422)
5. FastAPI returns JSON error
6. JavaScript catches error
7. Shows error toast to user
```
---
## ✅ Architecture Compliance
### Separation of Concerns ✅
| Layer | Responsibilities | ✅ |
|-------|-----------------|-----|
| **Exceptions** | Define error types | ✅ |
| **Service** | Business logic, validation, DB operations | ✅ |
| **API** | HTTP handling, auth, error conversion | ✅ |
| **Frontend** | User interaction, API calls | ✅ |
### What Goes Where
```python
# ❌ WRONG - Business logic in endpoint
@router.put("/{vendor_code}")
async def update_theme(...):
vendor = db.query(Vendor).filter(...).first() # ❌ Direct DB
if not vendor:
raise HTTPException(404) # ❌ Should be custom exception
theme.colors = theme_data.colors # ❌ Business logic
if not self._is_valid_color(theme.colors['primary']): # ❌ Validation
raise HTTPException(422)
db.commit() # ❌ Transaction management
return theme.to_dict()
# ✅ CORRECT - Clean separation
@router.put("/{vendor_code}")
async def update_theme(...):
try:
# Just call service
theme = vendor_theme_service.update_theme(db, vendor_code, theme_data)
return theme.to_dict()
except VendorNotFoundException:
raise HTTPException(404)
except ThemeValidationException as e:
raise HTTPException(422, detail=e.message)
```
---
## 🧪 Testing
### Unit Tests (Service Layer)
```python
# tests/unit/services/test_vendor_theme_service.py
def test_update_theme_validates_colors(service, db, vendor):
"""Test color validation."""
theme_data = VendorThemeUpdate(
colors={"primary": "not-a-color"} # Invalid
)
with pytest.raises(InvalidColorFormatException):
service.update_theme(db, vendor.vendor_code, theme_data)
def test_apply_preset_invalid_name(service, db, vendor):
"""Test invalid preset name."""
with pytest.raises(ThemePresetNotFoundException) as exc_info:
service.apply_theme_preset(db, vendor.vendor_code, "invalid")
assert "invalid" in str(exc_info.value.message)
```
### Integration Tests (API Layer)
```python
# tests/integration/api/v1/admin/test_vendor_themes.py
def test_update_theme_endpoint(client, admin_headers, vendor):
"""Test theme update endpoint."""
response = client.put(
f"/api/v1/admin/vendor-themes/{vendor.vendor_code}",
json={"colors": {"primary": "#ff0000"}},
headers=admin_headers
)
assert response.status_code == 200
assert response.json()["colors"]["primary"] == "#ff0000"
def test_update_theme_invalid_color(client, admin_headers, vendor):
"""Test validation error response."""
response = client.put(
f"/api/v1/admin/vendor-themes/{vendor.vendor_code}",
json={"colors": {"primary": "invalid"}},
headers=admin_headers
)
assert response.status_code == 422
assert "invalid color" in response.json()["detail"].lower()
```
---
## 📊 Comparison: Before vs After
### Before (Original) ❌
```python
# vendor_themes_api.py (OLD)
@router.put("/{vendor_code}")
async def update_vendor_theme(vendor_code: str, theme_data: dict, ...):
# ❌ Direct database access
vendor = db.query(Vendor).filter(...).first()
# ❌ Business logic in endpoint
if not theme:
theme = VendorTheme(vendor_id=vendor.id)
db.add(theme)
# ❌ Data manipulation in endpoint
if "colors" in theme_data:
theme.colors = theme_data["colors"]
# ❌ Transaction management in endpoint
db.commit()
db.refresh(theme)
# ❌ Generic exception
except Exception as e:
raise HTTPException(500, detail="Failed")
```
### After (Refactored) ✅
```python
# vendor_themes.py (NEW - API Layer)
@router.put("/{vendor_code}")
async def update_vendor_theme(vendor_code: str, theme_data: VendorThemeUpdate, ...):
try:
# ✅ Just call service
theme = vendor_theme_service.update_theme(db, vendor_code, theme_data)
return theme.to_dict()
# ✅ Specific exception handling
except VendorNotFoundException:
raise HTTPException(404, detail="Vendor not found")
except ThemeValidationException as e:
raise HTTPException(422, detail=e.message)
# vendor_theme_service.py (NEW - Service Layer)
class VendorThemeService:
def update_theme(self, db, vendor_code, theme_data):
try:
# ✅ Business logic here
vendor = self._get_vendor_by_code(db, vendor_code)
theme = self._get_or_create_theme(db, vendor)
# ✅ Validation
self._validate_theme_data(theme_data)
# ✅ Data updates
self._apply_theme_updates(theme, theme_data)
# ✅ Transaction management
db.commit()
return theme
# ✅ Custom exceptions
except ValidationError:
raise ThemeValidationException(...)
```
---
## 📚 Files Reference
### Backend Files (7 files)
1. **vendor_theme_exceptions.py**`app/exceptions/vendor_theme.py`
- Custom exception classes
2. **exceptions__init__-updated.py**`app/exceptions/__init__.py`
- Updated with vendor_theme imports
3. **vendor_theme_service.py**`app/services/vendor_theme_service.py`
- Business logic service
4. **vendor_themes_endpoints.py**`app/api/v1/admin/vendor_themes.py`
- API endpoints (thin layer)
5. **__init__-updated.py**`app/api/v1/admin/__init__.py`
- Router registration
6. **pages-updated.py**`app/api/v1/admin/pages.py`
- Frontend route
7. **vendor_theme_schemas.py**`models/schema/vendor_theme.py`
- Pydantic models
### Frontend Files (3 files)
8. **vendor-theme-alpine.js**`static/admin/js/vendor-theme.js`
9. **vendor-theme.html**`app/templates/admin/vendor-theme.html`
10. **theme_presets.py**`app/core/theme_presets.py`
---
## ✅ Final Checklist
### Backend Architecture
- [ ] Exceptions in `app/exceptions/vendor_theme.py`
- [ ] Service in `app/services/vendor_theme_service.py`
- [ ] Endpoints in `app/api/v1/admin/vendor_themes.py`
- [ ] No business logic in endpoints
- [ ] No HTTPException in service
- [ ] Custom exceptions used throughout
### Frontend Architecture
- [ ] Alpine.js component with `...data()`
- [ ] Uses lowercase `apiClient`
- [ ] Has initialization guard
- [ ] Follows dashboard.js pattern
### Integration
- [ ] Router registered in `__init__.py`
- [ ] Frontend route in `pages.py`
- [ ] All exceptions imported
- [ ] Service imported in endpoints
---
**Your theme editor now follows proper backend architecture with complete separation of concerns!** 🎯

View File

@@ -1,246 +0,0 @@
{# app/templates/shop/base.html #}
{# Base template for vendor shop frontend with theme support #}
<!DOCTYPE html>
<html lang="en" x-data="shopLayoutData()" x-bind:class="{ 'dark': dark }">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{# Dynamic title with vendor branding #}
<title>
{% block title %}{{ vendor.name }}{% endblock %}
{% if vendor.tagline %} - {{ vendor.tagline }}{% endif %}
</title>
{# SEO Meta Tags #}
<meta name="description" content="{% block meta_description %}{{ vendor.description or 'Shop at ' + vendor.name }}{% endblock %}">
<meta name="keywords" content="{% block meta_keywords %}{{ vendor.name }}, online shop{% endblock %}">
{# Favicon - vendor-specific or default #}
{% if theme.branding.favicon %}
<link rel="icon" type="image/x-icon" href="{{ theme.branding.favicon }}">
{% else %}
<link rel="icon" type="image/x-icon" href="{{ url_for('static', path='favicon.ico') }}">
{% endif %}
{# CRITICAL: Inject theme CSS variables #}
<style id="vendor-theme-variables">
:root {
{% for key, value in theme.css_variables.items() %}
{{ key }}: {{ value }};
{% endfor %}
}
{# Custom CSS from vendor theme #}
{% if theme.custom_css %}
{{ theme.custom_css | safe }}
{% endif %}
</style>
{# Tailwind CSS - uses CSS variables #}
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
{# Base Shop Styles #}
<link rel="stylesheet" href="{{ url_for('static', path='shop/css/shop.css') }}">
{# Optional: Theme-specific stylesheet #}
{% if theme.theme_name != 'default' %}
<link rel="stylesheet" href="{{ url_for('static', path='shop/themes/' + theme.theme_name + '.css') }}">
{% endif %}
{# Alpine.js for interactivity #}
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
{% block extra_head %}{% endblock %}
</head>
<body class="bg-gray-50 dark:bg-gray-900 transition-colors duration-200">
{# Header - Theme-aware #}
<header class="{% if theme.layout.header == 'fixed' %}sticky top-0 z-50{% endif %}
bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
{# Vendor Logo #}
<div class="flex items-center">
<a href="/" class="flex items-center space-x-3">
{% if theme.branding.logo %}
{# Show light logo in light mode, dark logo in dark mode #}
<img x-show="!dark"
src="{{ theme.branding.logo }}"
alt="{{ vendor.name }}"
class="h-8 w-auto">
{% if theme.branding.logo_dark %}
<img x-show="dark"
src="{{ theme.branding.logo_dark }}"
alt="{{ vendor.name }}"
class="h-8 w-auto">
{% endif %}
{% else %}
<span class="text-xl font-bold" style="color: var(--color-primary)">
{{ vendor.name }}
</span>
{% endif %}
</a>
</div>
{# Navigation #}
<nav class="hidden md:flex space-x-8">
<a href="/" class="text-gray-700 dark:text-gray-300 hover:text-primary">
Home
</a>
<a href="/products" class="text-gray-700 dark:text-gray-300 hover:text-primary">
Products
</a>
<a href="/about" class="text-gray-700 dark:text-gray-300 hover:text-primary">
About
</a>
<a href="/contact" class="text-gray-700 dark:text-gray-300 hover:text-primary">
Contact
</a>
</nav>
{# Right side actions #}
<div class="flex items-center space-x-4">
{# Search #}
<button @click="openSearch()" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</button>
{# Cart #}
<a href="/cart" class="relative p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
<span x-show="cartCount > 0"
x-text="cartCount"
class="absolute -top-1 -right-1 bg-accent text-white text-xs rounded-full h-5 w-5 flex items-center justify-center"
style="background-color: var(--color-accent)">
</span>
</a>
{# Theme toggle #}
<button @click="toggleTheme()"
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg x-show="!dark" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
</svg>
<svg x-show="dark" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
</svg>
</button>
{# Account #}
<a href="/account" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
</svg>
</a>
{# Mobile menu toggle #}
<button @click="toggleMobileMenu()" class="md:hidden p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
</div>
</div>
</header>
{# Main Content Area #}
<main class="min-h-screen">
{% block content %}
{# Page-specific content goes here #}
{% endblock %}
</main>
{# Footer with vendor info and social links #}
<footer class="bg-gray-100 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
{# Vendor Info #}
<div class="col-span-1 md:col-span-2">
<h3 class="text-lg font-semibold mb-4" style="color: var(--color-primary)">
{{ vendor.name }}
</h3>
<p class="text-gray-600 dark:text-gray-400 mb-4">
{{ vendor.description }}
</p>
{# Social Links from theme #}
{% if theme.social_links %}
<div class="flex space-x-4">
{% if theme.social_links.facebook %}
<a href="{{ theme.social_links.facebook }}" target="_blank"
class="text-gray-600 hover:text-primary dark:text-gray-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
</svg>
</a>
{% endif %}
{% if theme.social_links.instagram %}
<a href="{{ theme.social_links.instagram }}" target="_blank"
class="text-gray-600 hover:text-primary dark:text-gray-400">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
</svg>
</a>
{% endif %}
{# Add more social networks as needed #}
</div>
{% endif %}
</div>
{# Quick Links #}
<div>
<h4 class="font-semibold mb-4">Quick Links</h4>
<ul class="space-y-2">
<li><a href="/products" class="text-gray-600 hover:text-primary dark:text-gray-400">Products</a></li>
<li><a href="/about" class="text-gray-600 hover:text-primary dark:text-gray-400">About Us</a></li>
<li><a href="/contact" class="text-gray-600 hover:text-primary dark:text-gray-400">Contact</a></li>
<li><a href="/terms" class="text-gray-600 hover:text-primary dark:text-gray-400">Terms</a></li>
</ul>
</div>
{# Customer Service #}
<div>
<h4 class="font-semibold mb-4">Customer Service</h4>
<ul class="space-y-2">
<li><a href="/help" class="text-gray-600 hover:text-primary dark:text-gray-400">Help Center</a></li>
<li><a href="/shipping" class="text-gray-600 hover:text-primary dark:text-gray-400">Shipping Info</a></li>
<li><a href="/returns" class="text-gray-600 hover:text-primary dark:text-gray-400">Returns</a></li>
<li><a href="/faq" class="text-gray-600 hover:text-primary dark:text-gray-400">FAQ</a></li>
</ul>
</div>
</div>
{# Copyright #}
<div class="mt-8 pt-8 border-t border-gray-200 dark:border-gray-700 text-center text-gray-600 dark:text-gray-400">
<p>&copy; {{ now().year }} {{ vendor.name }}. All rights reserved.</p>
</div>
</div>
</footer>
{# Base Shop JavaScript #}
<script src="{{ url_for('static', path='shop/js/shop-layout.js') }}"></script>
{# Page-specific JavaScript #}
{% block extra_scripts %}{% endblock %}
{# Toast notification container #}
<div id="toast-container" class="fixed bottom-4 right-4 z-50"></div>
</body>
</html>

View File

@@ -1,228 +0,0 @@
// static/shop/js/shop-layout.js
/**
* Shop Layout Component
* Provides base functionality for vendor shop pages
* Works with vendor-specific themes
*/
const shopLog = {
info: (...args) => console.info('🛒 [SHOP]', ...args),
warn: (...args) => console.warn('⚠️ [SHOP]', ...args),
error: (...args) => console.error('❌ [SHOP]', ...args),
debug: (...args) => console.log('🔍 [SHOP]', ...args)
};
/**
* Shop Layout Data
* Base Alpine.js component for shop pages
*/
function shopLayoutData() {
return {
// Theme state
dark: localStorage.getItem('shop-theme') === 'dark',
// UI state
mobileMenuOpen: false,
searchOpen: false,
cartCount: 0,
// Cart state
cart: [],
// Initialize
init() {
shopLog.info('Shop layout initializing...');
// Load cart from localStorage
this.loadCart();
// Listen for cart updates
window.addEventListener('cart-updated', () => {
this.loadCart();
});
shopLog.info('Shop layout initialized');
},
// Theme management
toggleTheme() {
this.dark = !this.dark;
localStorage.setItem('shop-theme', this.dark ? 'dark' : 'light');
shopLog.debug('Theme toggled:', this.dark ? 'dark' : 'light');
},
// Mobile menu
toggleMobileMenu() {
this.mobileMenuOpen = !this.mobileMenuOpen;
if (this.mobileMenuOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
},
closeMobileMenu() {
this.mobileMenuOpen = false;
document.body.style.overflow = '';
},
// Search
openSearch() {
this.searchOpen = true;
shopLog.debug('Search opened');
// Focus search input after a short delay
setTimeout(() => {
const input = document.querySelector('#search-input');
if (input) input.focus();
}, 100);
},
closeSearch() {
this.searchOpen = false;
},
// Cart management
loadCart() {
try {
const cartData = localStorage.getItem('shop-cart');
if (cartData) {
this.cart = JSON.parse(cartData);
this.cartCount = this.cart.reduce((sum, item) => sum + item.quantity, 0);
}
} catch (error) {
shopLog.error('Failed to load cart:', error);
this.cart = [];
this.cartCount = 0;
}
},
addToCart(product, quantity = 1) {
shopLog.info('Adding to cart:', product.name, 'x', quantity);
// Find existing item
const existingIndex = this.cart.findIndex(item => item.id === product.id);
if (existingIndex !== -1) {
// Update quantity
this.cart[existingIndex].quantity += quantity;
} else {
// Add new item
this.cart.push({
id: product.id,
name: product.name,
price: product.price,
image: product.image,
quantity: quantity
});
}
// Save and update
this.saveCart();
this.showToast(`${product.name} added to cart`, 'success');
},
updateCartItem(productId, quantity) {
const index = this.cart.findIndex(item => item.id === productId);
if (index !== -1) {
if (quantity <= 0) {
this.cart.splice(index, 1);
} else {
this.cart[index].quantity = quantity;
}
this.saveCart();
}
},
removeFromCart(productId) {
this.cart = this.cart.filter(item => item.id !== productId);
this.saveCart();
this.showToast('Item removed from cart', 'info');
},
clearCart() {
this.cart = [];
this.saveCart();
this.showToast('Cart cleared', 'info');
},
saveCart() {
try {
localStorage.setItem('shop-cart', JSON.stringify(this.cart));
this.cartCount = this.cart.reduce((sum, item) => sum + item.quantity, 0);
// Dispatch custom event
window.dispatchEvent(new CustomEvent('cart-updated'));
shopLog.debug('Cart saved:', this.cart.length, 'items');
} catch (error) {
shopLog.error('Failed to save cart:', error);
}
},
// Get cart total
get cartTotal() {
return this.cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
},
// Toast notifications
showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
if (!container) return;
const toast = document.createElement('div');
toast.className = `toast toast-${type} transform transition-all duration-300 mb-2`;
// Color based on type
const colors = {
success: 'bg-green-500',
error: 'bg-red-500',
warning: 'bg-yellow-500',
info: 'bg-blue-500'
};
toast.innerHTML = `
<div class="${colors[type]} text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-3">
<span>${message}</span>
<button onclick="this.parentElement.parentElement.remove()"
class="ml-4 hover:opacity-75">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
`;
container.appendChild(toast);
// Auto-remove after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 3000);
},
// Format currency
formatPrice(price) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price);
},
// Format date
formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
};
}
// Make available globally
window.shopLayoutData = shopLayoutData;
shopLog.info('Shop layout module loaded');

View File

@@ -1,124 +0,0 @@
# middleware/theme_context.py
"""
Theme Context Middleware
Injects vendor-specific theme into request context
"""
import logging
from fastapi import Request
from sqlalchemy.orm import Session
from app.core.database import get_db
from models.database.vendor_theme import VendorTheme
logger = logging.getLogger(__name__)
class ThemeContextManager:
"""Manages theme context for vendor shops."""
@staticmethod
def get_vendor_theme(db: Session, vendor_id: int) -> dict:
"""
Get theme configuration for vendor.
Returns default theme if no custom theme is configured.
"""
theme = db.query(VendorTheme).filter(
VendorTheme.vendor_id == vendor_id,
VendorTheme.is_active == True
).first()
if theme:
return theme.to_dict()
# Return default theme
return get_default_theme()
@staticmethod
def get_default_theme() -> dict:
"""Default theme configuration"""
return {
"theme_name": "default",
"colors": {
"primary": "#6366f1",
"secondary": "#8b5cf6",
"accent": "#ec4899",
"background": "#ffffff",
"text": "#1f2937",
"border": "#e5e7eb"
},
"fonts": {
"heading": "Inter, sans-serif",
"body": "Inter, sans-serif"
},
"branding": {
"logo": None,
"logo_dark": None,
"favicon": None,
"banner": None
},
"layout": {
"style": "grid",
"header": "fixed",
"product_card": "modern"
},
"social_links": {},
"custom_css": None,
"css_variables": {
"--color-primary": "#6366f1",
"--color-secondary": "#8b5cf6",
"--color-accent": "#ec4899",
"--color-background": "#ffffff",
"--color-text": "#1f2937",
"--color-border": "#e5e7eb",
"--font-heading": "Inter, sans-serif",
"--font-body": "Inter, sans-serif",
}
}
async def theme_context_middleware(request: Request, call_next):
"""
Middleware to inject theme context into request state.
This runs AFTER vendor_context_middleware has set request.state.vendor
"""
# Only inject theme for shop pages (not admin or API)
if hasattr(request.state, 'vendor') and request.state.vendor:
vendor = request.state.vendor
# Get database session
db_gen = get_db()
db = next(db_gen)
try:
# Get vendor theme
theme = ThemeContextManager.get_vendor_theme(db, vendor.id)
request.state.theme = theme
logger.debug(
f"Theme loaded for vendor {vendor.name}: {theme['theme_name']}"
)
except Exception as e:
logger.error(f"Failed to load theme for vendor {vendor.id}: {e}")
# Fallback to default theme
request.state.theme = ThemeContextManager.get_default_theme()
finally:
db.close()
else:
# No vendor context, use default theme
request.state.theme = ThemeContextManager.get_default_theme()
response = await call_next(request)
return response
def get_current_theme(request: Request) -> dict:
"""Helper function to get current theme from request state."""
return getattr(request.state, "theme", ThemeContextManager.get_default_theme())
# Add to main.py after vendor_context_middleware:
"""
# Add theme context middleware (must be after vendor context)
app.middleware("http")(theme_context_middleware)
"""

View File

@@ -1,143 +0,0 @@
# models/database/vendor_theme.py
"""
Vendor Theme Configuration Model
Allows each vendor to customize their shop's appearance
"""
from datetime import datetime, timezone
from sqlalchemy import Column, Integer, String, Boolean, Text, JSON, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from app.core.database import Base
class VendorTheme(Base):
"""
Stores theme configuration for each vendor's shop.
Each vendor can have:
- Custom colors (primary, secondary, accent)
- Custom fonts
- Custom logo and favicon
- Custom CSS overrides
- Layout preferences
"""
__tablename__ = "vendor_themes"
id = Column(Integer, primary_key=True, index=True)
vendor_id = Column(Integer, ForeignKey("vendors.id", ondelete="CASCADE"), nullable=False, unique=True)
# Basic Theme Settings
theme_name = Column(String(100), default="default") # e.g., "modern", "classic", "minimal"
is_active = Column(Boolean, default=True)
# Color Scheme (JSON for flexibility)
colors = Column(JSON, default={
"primary": "#6366f1", # Indigo
"secondary": "#8b5cf6", # Purple
"accent": "#ec4899", # Pink
"background": "#ffffff", # White
"text": "#1f2937", # Gray-800
"border": "#e5e7eb" # Gray-200
})
# Typography
font_family_heading = Column(String(100), default="Inter, sans-serif")
font_family_body = Column(String(100), default="Inter, sans-serif")
# Branding Assets
logo_url = Column(String(500), nullable=True) # Path to vendor logo
logo_dark_url = Column(String(500), nullable=True) # Dark mode logo
favicon_url = Column(String(500), nullable=True) # Favicon
banner_url = Column(String(500), nullable=True) # Homepage banner
# Layout Preferences
layout_style = Column(String(50), default="grid") # grid, list, masonry
header_style = Column(String(50), default="fixed") # fixed, static, transparent
product_card_style = Column(String(50), default="modern") # modern, classic, minimal
# Custom CSS (for advanced customization)
custom_css = Column(Text, nullable=True)
# Social Media Links
social_links = Column(JSON, default={}) # {facebook: "url", instagram: "url", etc.}
# SEO & Meta
meta_title_template = Column(String(200), nullable=True) # e.g., "{product_name} - {shop_name}"
meta_description = Column(Text, nullable=True)
# Timestamps
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
updated_at = Column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
# Relationships
vendor = relationship("Vendor", back_populates="theme")
def __repr__(self):
return f"<VendorTheme(vendor_id={self.vendor_id}, theme_name='{self.theme_name}')>"
@property
def primary_color(self):
"""Get primary color from JSON"""
return self.colors.get("primary", "#6366f1")
@property
def css_variables(self):
"""Generate CSS custom properties from theme config"""
return {
"--color-primary": self.colors.get("primary", "#6366f1"),
"--color-secondary": self.colors.get("secondary", "#8b5cf6"),
"--color-accent": self.colors.get("accent", "#ec4899"),
"--color-background": self.colors.get("background", "#ffffff"),
"--color-text": self.colors.get("text", "#1f2937"),
"--color-border": self.colors.get("border", "#e5e7eb"),
"--font-heading": self.font_family_heading,
"--font-body": self.font_family_body,
}
def to_dict(self):
"""Convert theme to dictionary for template rendering"""
return {
"theme_name": self.theme_name,
"colors": self.colors,
"fonts": {
"heading": self.font_family_heading,
"body": self.font_family_body,
},
"branding": {
"logo": self.logo_url,
"logo_dark": self.logo_dark_url,
"favicon": self.favicon_url,
"banner": self.banner_url,
},
"layout": {
"style": self.layout_style,
"header": self.header_style,
"product_card": self.product_card_style,
},
"social_links": self.social_links,
"custom_css": self.custom_css,
"css_variables": self.css_variables,
}
# Update Vendor model to include theme relationship
"""
Add to models/database/vendor.py:
theme = relationship(
"VendorTheme",
back_populates="vendor",
uselist=False,
cascade="all, delete-orphan"
)
@property
def active_theme(self):
'''Get vendor's active theme or return default'''
if self.theme and self.theme.is_active:
return self.theme
return None
"""

View File

@@ -1,567 +0,0 @@
# Vendor Domains - Refactored Architecture Implementation Guide
## Overview
This document explains the refactored vendor domains implementation that follows your application's architecture patterns with proper separation of concerns.
## Architecture Summary
The implementation follows these key principles:
1. **Separation of Concerns**: Endpoints, service layer, schemas, and database models are separated
2. **Exception-Based Error Handling**: Custom exceptions with proper HTTP status codes
3. **Service Layer Pattern**: Business logic isolated in service classes
4. **Pydantic Validation**: Input validation using Pydantic schemas
5. **Consistent Response Format**: Standardized response models
## File Structure
```
app/
├── api/
│ └── v1/
│ └── admin/
│ └── vendor_domains.py # HTTP endpoints (NEW)
├── services/
│ └── vendor_domain_service.py # Business logic (NEW)
├── exceptions/
│ ├── __init__.py # Updated exports
│ └── vendor_domain.py # Domain exceptions (NEW)
models/
├── schema/
│ └── vendor_domain.py # Pydantic schemas (NEW)
└── database/
├── vendor.py # Updated Vendor model
└── vendor_domain.py # VendorDomain model
```
## Components
### 1. Exceptions (`app/exceptions/vendor_domain.py`)
Custom exceptions for domain operations:
```python
# Resource Not Found (404)
- VendorDomainNotFoundException
# Conflicts (409)
- VendorDomainAlreadyExistsException
# Validation (422)
- InvalidDomainFormatException
- ReservedDomainException
# Business Logic (400)
- DomainNotVerifiedException
- DomainVerificationFailedException
- DomainAlreadyVerifiedException
- MultiplePrimaryDomainsException
- MaxDomainsReachedException
- UnauthorizedDomainAccessException
# External Service (502)
- DNSVerificationException
```
**Key Features:**
- Inherit from appropriate base exceptions
- Include relevant context in `details` dict
- Proper HTTP status codes
- Clear, actionable error messages
### 2. Pydantic Schemas (`models/schema/vendor_domain.py`)
Input validation and response models:
```python
# Request Schemas
- VendorDomainCreate # For adding domains
- VendorDomainUpdate # For updating settings
# Response Schemas
- VendorDomainResponse # Single domain
- VendorDomainListResponse # List of domains
- DomainVerificationInstructions # DNS instructions
- DomainVerificationResponse # Verification result
- DomainDeletionResponse # Deletion confirmation
```
**Validation Features:**
- Domain normalization (lowercase, remove protocol)
- Reserved subdomain checking
- Format validation using regex
- Field validators with custom error messages
### 3. Service Layer (`app/services/vendor_domain_service.py`)
Business logic and database operations:
```python
class VendorDomainService:
# Core Operations
add_domain() # Add custom domain
get_vendor_domains() # List vendor's domains
get_domain_by_id() # Get single domain
update_domain() # Update settings
delete_domain() # Remove domain
# Verification
verify_domain() # DNS verification
get_verification_instructions() # Get DNS instructions
# Private Helpers
_get_vendor_by_id_or_raise() # Vendor lookup
_check_domain_limit() # Enforce max domains
_domain_exists() # Check uniqueness
_validate_domain_format() # Format validation
_unset_primary_domains() # Primary domain logic
```
**Service Pattern:**
- All database operations in service layer
- Raises custom exceptions (not HTTPException)
- Transaction management (commit/rollback)
- Comprehensive logging
- Helper methods with `_` prefix for internal use
### 4. API Endpoints (`app/api/v1/admin/vendor_domains.py`)
HTTP layer for domain management:
```python
# Endpoints
POST /vendors/{vendor_id}/domains # Add domain
GET /vendors/{vendor_id}/domains # List domains
GET /vendors/domains/{domain_id} # Get domain
PUT /vendors/domains/{domain_id} # Update domain
DELETE /vendors/domains/{domain_id} # Delete domain
POST /vendors/domains/{domain_id}/verify # Verify ownership
GET /vendors/domains/{domain_id}/verification-instructions # Get instructions
```
**Endpoint Pattern:**
- Only handle HTTP concerns (request/response)
- Delegate business logic to service layer
- Use Pydantic schemas for validation
- Proper dependency injection
- Comprehensive docstrings
- No direct database access
## Comparison: Old vs New
### Old Implementation Issues
```python
# ❌ Mixed concerns
@router.post("/{vendor_id}/domains")
def add_vendor_domain(...):
# Validation in endpoint
domain = VendorDomain.normalize_domain(domain_data.domain)
# Business logic in endpoint
if not domain or '/' in domain:
raise HTTPException(400, "Invalid domain")
# Database operations in endpoint
existing = db.query(VendorDomain).filter(...).first()
if existing:
raise HTTPException(409, "Domain exists")
# Direct database access
domain = VendorDomain(...)
db.add(domain)
db.commit()
```
**Problems:**
- Business logic mixed with HTTP layer
- HTTPException instead of custom exceptions
- No service layer separation
- Direct database access in endpoints
- Validation scattered across endpoint
- Hard to test business logic
### New Implementation
```python
# ✅ Proper separation
# Endpoint (HTTP layer only)
@router.post("/{vendor_id}/domains", response_model=VendorDomainResponse)
def add_vendor_domain(
vendor_id: int = Path(..., gt=0),
domain_data: VendorDomainCreate = Body(...), # Pydantic validation
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Add domain - delegates to service layer"""
domain = vendor_domain_service.add_domain(db, vendor_id, domain_data)
return VendorDomainResponse(...mapping...)
# Service (Business logic)
class VendorDomainService:
def add_domain(self, db, vendor_id, domain_data):
"""Business logic and database operations"""
# Verify vendor
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
# Check limits
self._check_domain_limit(db, vendor_id)
# Validate format
self._validate_domain_format(normalized_domain)
# Check uniqueness
if self._domain_exists(db, normalized_domain):
raise VendorDomainAlreadyExistsException(...) # Custom exception
# Business logic
if domain_data.is_primary:
self._unset_primary_domains(db, vendor_id)
# Database operations
new_domain = VendorDomain(...)
db.add(new_domain)
db.commit()
return new_domain
# Schema (Validation)
class VendorDomainCreate(BaseModel):
domain: str
is_primary: bool = False
@field_validator('domain')
def validate_domain(cls, v: str) -> str:
"""Normalize and validate domain"""
# Validation logic here
return normalized_domain
```
**Benefits:**
- Clean separation of concerns
- Custom exceptions with proper status codes
- Testable business logic
- Reusable service methods
- Centralized validation
- Easy to maintain
## Installation Steps
### 1. Add Exception File
```bash
# Create new exception file
app/exceptions/vendor_domain.py
```
Copy content from `vendor_domain_exceptions.py`
### 2. Update Exception Exports
```python
# app/exceptions/__init__.py
from .vendor_domain import (
VendorDomainNotFoundException,
VendorDomainAlreadyExistsException,
# ... other exceptions
)
__all__ = [
# ... existing exports
"VendorDomainNotFoundException",
"VendorDomainAlreadyExistsException",
# ... other exports
]
```
### 3. Add Pydantic Schemas
```bash
# Create schema file
models/schema/vendor_domain.py
```
Copy content from `vendor_domain_schema.py`
### 4. Add Service Layer
```bash
# Create service file
app/services/vendor_domain_service.py
```
Copy content from `vendor_domain_service.py`
### 5. Replace Endpoint File
```bash
# Replace existing file
app/api/v1/admin/vendor_domains.py
```
Copy content from `vendor_domains.py`
### 6. Install DNS Library
```bash
pip install dnspython
```
Required for DNS verification functionality.
## Usage Examples
### Adding a Domain
```python
# Request
POST /api/v1/admin/vendors/1/domains
{
"domain": "myshop.com",
"is_primary": true
}
# Response (201)
{
"id": 1,
"vendor_id": 1,
"domain": "myshop.com",
"is_primary": true,
"is_active": false,
"is_verified": false,
"ssl_status": "pending",
"verification_token": "abc123...",
"verified_at": null,
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-15T10:00:00Z"
}
```
### Domain Verification
```python
# Step 1: Get verification instructions
GET /api/v1/admin/vendors/domains/1/verification-instructions
# Response
{
"domain": "myshop.com",
"verification_token": "abc123xyz...",
"instructions": {
"step1": "Go to your domain's DNS settings",
"step2": "Add a new TXT record with the following values:",
"step3": "Wait for DNS propagation (5-15 minutes)",
"step4": "Click 'Verify Domain' button"
},
"txt_record": {
"type": "TXT",
"name": "_wizamart-verify",
"value": "abc123xyz...",
"ttl": 3600
}
}
# Step 2: Vendor adds DNS record
# _wizamart-verify.myshop.com TXT "abc123xyz..."
# Step 3: Verify domain
POST /api/v1/admin/vendors/domains/1/verify
# Response (200)
{
"message": "Domain myshop.com verified successfully",
"domain": "myshop.com",
"verified_at": "2025-01-15T10:15:00Z",
"is_verified": true
}
```
### Activating Domain
```python
# After verification, activate domain
PUT /api/v1/admin/vendors/domains/1
{
"is_active": true
}
# Response (200)
{
"id": 1,
"vendor_id": 1,
"domain": "myshop.com",
"is_primary": true,
"is_active": true,
"is_verified": true,
"ssl_status": "pending",
"verified_at": "2025-01-15T10:15:00Z",
...
}
```
## Error Handling Examples
### Domain Already Exists
```python
POST /api/v1/admin/vendors/1/domains
{
"domain": "existing.com"
}
# Response (409 Conflict)
{
"error_code": "VENDOR_DOMAIN_ALREADY_EXISTS",
"message": "Domain 'existing.com' is already registered",
"status_code": 409,
"details": {
"domain": "existing.com",
"existing_vendor_id": 2
}
}
```
### Invalid Domain Format
```python
POST /api/v1/admin/vendors/1/domains
{
"domain": "admin.example.com" # Reserved subdomain
}
# Response (422 Validation Error)
{
"error_code": "RESERVED_DOMAIN",
"message": "Domain cannot use reserved subdomain: admin",
"status_code": 422,
"details": {
"domain": "admin.example.com",
"reserved_part": "admin"
}
}
```
### Verification Failed
```python
POST /api/v1/admin/vendors/domains/1/verify
# Response (400 Bad Request)
{
"error_code": "DOMAIN_VERIFICATION_FAILED",
"message": "Domain verification failed for 'myshop.com': Verification token not found in DNS records",
"status_code": 400,
"details": {
"domain": "myshop.com",
"reason": "Verification token not found in DNS records"
}
}
```
## Testing
### Unit Tests
```python
# tests/unit/services/test_vendor_domain_service.py
def test_add_domain_success(db_session):
"""Test successful domain addition"""
service = VendorDomainService()
domain_data = VendorDomainCreate(
domain="test.com",
is_primary=True
)
domain = service.add_domain(db_session, vendor_id=1, domain_data=domain_data)
assert domain.domain == "test.com"
assert domain.is_primary is True
assert domain.is_verified is False
def test_add_domain_already_exists(db_session):
"""Test adding duplicate domain raises exception"""
service = VendorDomainService()
with pytest.raises(VendorDomainAlreadyExistsException):
service.add_domain(db_session, vendor_id=1, domain_data=...)
```
### Integration Tests
```python
# tests/integration/api/test_vendor_domains.py
def test_add_domain_endpoint(client, admin_headers):
"""Test domain addition endpoint"""
response = client.post(
"/api/v1/admin/vendors/1/domains",
json={"domain": "newshop.com", "is_primary": False},
headers=admin_headers
)
assert response.status_code == 201
data = response.json()
assert data["domain"] == "newshop.com"
assert data["is_verified"] is False
def test_verify_domain_not_found(client, admin_headers):
"""Test verification with non-existent domain"""
response = client.post(
"/api/v1/admin/vendors/domains/99999/verify",
headers=admin_headers
)
assert response.status_code == 404
assert response.json()["error_code"] == "VENDOR_DOMAIN_NOT_FOUND"
```
## Benefits of This Architecture
### 1. Maintainability
- Clear separation makes code easy to understand
- Changes isolated to appropriate layers
- Easy to locate and fix bugs
### 2. Testability
- Service layer can be unit tested independently
- Mock dependencies easily
- Integration tests for endpoints
### 3. Reusability
- Service methods can be called from anywhere
- Schemas reused across endpoints
- Exceptions standardized
### 4. Scalability
- Add new endpoints without duplicating logic
- Extend service layer for new features
- Easy to add caching, queuing, etc.
### 5. Error Handling
- Consistent error responses
- Proper HTTP status codes
- Detailed error information for debugging
## Next Steps
1. **Database Migration**: Create migration for vendor_domains table if not exists
2. **Middleware Update**: Update vendor detection middleware to check custom domains
3. **Frontend Integration**: Build UI for domain management
4. **SSL Automation**: Add automatic SSL certificate provisioning
5. **Monitoring**: Add logging and monitoring for domain operations
6. **Rate Limiting**: Implement rate limits for domain additions
7. **Webhooks**: Add webhooks for domain status changes
## Conclusion
This refactored implementation follows your application's architecture patterns:
- ✅ Proper separation of concerns
- ✅ Exception-based error handling
- ✅ Service layer for business logic
- ✅ Pydantic schemas for validation
- ✅ Clean, maintainable code
- ✅ Consistent with existing patterns (vendors.py example)
The code is now production-ready, maintainable, and follows best practices!

View File

@@ -1,298 +0,0 @@
# RBAC Implementation - Deliverables Index
## Overview
This package contains a complete RBAC (Role-Based Access Control) implementation for your multi-tenant e-commerce platform. All files are ready to be integrated into your codebase.
## 📚 Developer Documentation
### For Your Development Team
**[RBAC_DEVELOPER_GUIDE.md](RBAC_DEVELOPER_GUIDE.md)** ⭐ **Primary Documentation**
- Complete developer guide (800+ lines)
- Architecture overview and design principles
- Database schema with detailed explanations
- Permission system structure
- Authentication and authorization flows
- Team management workflows
- Extensive code examples for all scenarios
- Best practices and patterns
- Comprehensive testing guidelines
- Troubleshooting section
- **Give this to your developers** - Everything they need to know
**[RBAC_QUICK_REFERENCE.md](RBAC_QUICK_REFERENCE.md)** 📋 **Quick Reference**
- One-page reference for daily development
- Common imports and patterns
- Permission constants lookup
- Helper methods cheat sheet
- Frontend integration snippets
- Testing patterns
- Debugging commands
- **Print and keep at desk** - For quick lookups
### For Implementation Planning
**[RBAC_IMPLEMENTATION_SUMMARY.md](RBAC_IMPLEMENTATION_SUMMARY.md)**
- Executive summary and key recommendations
- Architecture overview and permission hierarchy
- Security best practices
- Common pitfalls to avoid
- Implementation checklist
- Q&A section
- **For planning** - Read this first for the big picture
**[RBAC_VISUAL_GUIDE.md](RBAC_VISUAL_GUIDE.md)**
- System architecture diagrams
- Team invitation flow
- Permission check flow
- Database relationship diagrams
- Permission naming conventions
- Role preset visualizations
- Security boundaries
- **For understanding** - Visual learners start here
## 🗄️ Database Models
### **user_model_improved.py**
Updated User model with:
- Clarified role field (admin/vendor only)
- Email verification field
- Helper methods for permission checking
- Owner/member checking methods
- Vendor-specific role retrieval
**Where to integrate:** Replace/update your `models/database/user.py`
### **vendor_user_improved.py**
Enhanced VendorUser model with:
- `user_type` field (owner/member distinction)
- Invitation system fields
- Permission checking methods
- Owner identification
**Where to integrate:** Update your `models/database/vendor.py` (VendorUser class)
## 🔐 Permission System
### **permissions.py**
Complete permission system including:
- `VendorPermissions` enum (all available permissions)
- `PermissionGroups` class (preset role configurations)
- `PermissionChecker` utility class
- Helper functions
**Where to integrate:** Create as `app/core/permissions.py`
## ⚠️ Exception Handling
### **vendor_exceptions.py**
Vendor-specific exceptions:
- `VendorAccessDeniedException`
- `InsufficientVendorPermissionsException`
- `VendorOwnerOnlyException`
- `CannotRemoveVendorOwnerException`
- `TeamMemberAlreadyExistsException`
- `InvalidInvitationTokenException`
- And more...
**Where to integrate:** Add to your `app/exceptions/` directory
## 🛠️ Dependencies & Route Guards
### **deps_permissions.py**
FastAPI dependencies for permission checking:
- `require_vendor_permission(permission)`
- `require_vendor_owner()`
- `require_any_vendor_permission(*permissions)`
- `require_all_vendor_permissions(*permissions)`
- `get_user_permissions()`
**Where to integrate:** Add to your existing `app/api/deps.py`
## 🔧 Service Layer
### **vendor_team_service.py**
Complete team management service:
- `invite_team_member()` - Send team invitations
- `accept_invitation()` - Activate invited accounts
- `remove_team_member()` - Remove team members
- `update_member_role()` - Change member roles
- `get_team_members()` - List all team members
- Helper methods for token generation and role management
**Where to integrate:** Create as `app/services/vendor_team_service.py`
## 📡 API Routes
### **team_routes_example.py**
Complete team management API routes:
- GET `/team/members` - List team members
- POST `/team/invite` - Invite new member
- POST `/team/accept-invitation` - Accept invitation
- DELETE `/team/members/{user_id}` - Remove member
- PUT `/team/members/{user_id}/role` - Update member role
- GET `/team/me/permissions` - Get current user permissions
- Example product routes with permission checks
**Where to integrate:** Create as `app/api/v1/vendor/team.py`
## 🗃️ Database Migration
### **rbac_migration_guide.md**
Comprehensive migration guide:
- Schema changes required
- Alembic migration script
- Data migration steps (6 steps with SQL)
- Post-migration checklist
- Verification queries
- Rollback plan
**Use this:** Before deploying to production
## 📊 File Structure
```
your-project/
├── app/
│ ├── api/
│ │ ├── deps.py ← Add deps_permissions.py content
│ │ └── v1/
│ │ ├── admin/
│ │ └── vendor/
│ │ └── team.py ← Add team_routes_example.py
│ │
│ ├── core/
│ │ └── permissions.py ← Add permissions.py
│ │
│ ├── exceptions/
│ │ └── vendor.py ← Add vendor_exceptions.py content
│ │
│ └── services/
│ └── vendor_team_service.py ← Add vendor_team_service.py
└── models/
└── database/
├── user.py ← Update with user_model_improved.py
└── vendor.py ← Update VendorUser from vendor_user_improved.py
```
## 🚀 Quick Start Integration
### Step 1: Review & Plan (30 minutes)
1. Read `RBAC_IMPLEMENTATION_SUMMARY.md`
2. Review `RBAC_VISUAL_GUIDE.md` for architecture
3. Check your current codebase against recommendations
### Step 2: Database Migration (1-2 hours)
1. Follow `rbac_migration_guide.md`
2. Create Alembic migration
3. Test in development environment
4. Run migration
### Step 3: Integrate Models (1 hour)
1. Update `models/database/user.py` with improved User model
2. Update `models/database/vendor.py` with improved VendorUser
3. Test model loading
### Step 4: Add Permission System (30 minutes)
1. Create `app/core/permissions.py`
2. Import in your application
3. Test permission constants
### Step 5: Add Exceptions (15 minutes)
1. Add vendor exceptions to `app/exceptions/`
2. Import in relevant modules
### Step 6: Add Dependencies (30 minutes)
1. Add permission checking functions to `app/api/deps.py`
2. Test dependencies work
### Step 7: Add Service Layer (30 minutes)
1. Create `app/services/vendor_team_service.py`
2. Test service methods
### Step 8: Add Routes (30 minutes)
1. Create team management routes
2. Add permission checks to existing routes
3. Test all endpoints
### Step 9: Frontend Integration (2-4 hours)
1. Update login flow to fetch permissions
2. Add UI elements for team management
3. Show/hide features based on permissions
4. Create invitation acceptance page
### Step 10: Testing (2-3 hours)
1. Test all permission combinations
2. Test invitation flow
3. Test owner protections
4. Test admin blocking
5. Test multi-vendor access
## 📝 Implementation Notes
### Priority Changes (Must Do)
1. **User.role clarification** - Critical for security
2. **VendorUser.user_type** - Required for owner distinction
3. **Permission checking in routes** - Security requirement
4. **Invitation system** - Required for team management
### Optional Enhancements
1. **Custom permissions UI** - Allow owners to create custom roles
2. **Permission analytics** - Track permission usage
3. **Team activity logs** - Audit trail for team actions
4. **Email templates** - Professional invitation emails
## 🆘 Support & Questions
### Common Issues
**Q: Migration fails?**
A: Check the verification queries in the migration guide. Likely data inconsistency.
**Q: Permission checking not working?**
A: Ensure middleware sets `request.state.vendor` correctly.
**Q: Owner can't access routes?**
A: Check that owner has `VendorUser` entry with `user_type='owner'`.
**Q: Invitation emails not sending?**
A: Implement `_send_invitation_email()` in service (marked as TODO).
### Next Steps for You
1. ✅ Review all documentation
2. ✅ Plan integration timeline
3. ✅ Set up development environment
4. ✅ Run database migration in dev
5. ✅ Integrate code changes
6. ✅ Test thoroughly
7. ✅ Deploy to staging
8. ✅ User acceptance testing
9. ✅ Deploy to production
## 📞 Your Questions Answered
Based on your original question:
**✅ Admin Creation:** Admins created by super admins on backend *(already correct)*
**✅ Vendor Owner Creation:** Auto-created when vendor is created *(implement in vendor creation logic)*
**✅ Team Member Invitation:** Email-based invitation system *(vendor_team_service.py provides this)*
**✅ Customer Registration:** Self-registration on shop *(separate Customer model is correct)*
**✅ Role-Based Access:** Full RBAC system *(permissions.py + dependencies)*
**✅ Multi-Tenant Isolation:** Vendor-scoped roles and permissions *(VendorUser + Role models)*
## 🎉 You're Ready!
You now have everything needed to implement a production-ready RBAC system. All code is written, tested patterns are provided, and comprehensive documentation is included.
Good luck with your implementation! 🚀

File diff suppressed because it is too large Load Diff

View File

@@ -1,474 +0,0 @@
# RBAC Implementation Recommendations - Summary
## Executive Summary
Your current authentication and authorization system is well-structured. Here are my recommendations to enhance it for proper role-based access control (RBAC) in your multi-tenant e-commerce platform.
## Key Recommendations
### 1. ✅ Clarify User.role Field
**Current Issue:** The `User.role` field is used for both platform-level and vendor-specific roles.
**Solution:**
- `User.role` should ONLY contain: `"admin"` or `"vendor"`
- Vendor-specific roles (manager, staff, etc.) belong in `VendorUser.role`
- Customers are separate in the `Customer` model (already correct)
**Benefits:**
- Clear separation of concerns
- Easier to manage platform-level access
- Prevents confusion between contexts
### 2. ✅ Add VendorUser.user_type Field
**Why:** Distinguish between vendor owners and team members at the database level.
**Implementation:**
```python
class VendorUserType(str, enum.Enum):
OWNER = "owner"
TEAM_MEMBER = "member"
```
**Benefits:**
- Owners have automatic full permissions
- Team members use role-based permissions
- Easy to query for owners vs. members
- Prevents accidentally removing owners
### 3. ✅ Implement Invitation System
**Components Needed:**
- `invitation_token` field in VendorUser
- `invitation_sent_at` and `invitation_accepted_at` timestamps
- Email sending service
- Invitation acceptance endpoint
**Flow:**
1. Owner invites team member via email
2. System creates User account (inactive) and VendorUser (pending)
3. Invitation email sent with unique token
4. Team member clicks link, sets password, activates account
5. VendorUser.is_active set to True
### 4. ✅ Define Permission Constants
**Create:** `app/core/permissions.py` with:
- `VendorPermissions` enum (all available permissions)
- `PermissionGroups` class (preset role permissions)
- `PermissionChecker` utility class
**Example Permissions:**
```python
PRODUCTS_VIEW = "products.view"
PRODUCTS_CREATE = "products.create"
PRODUCTS_DELETE = "products.delete"
ORDERS_VIEW = "orders.view"
TEAM_INVITE = "team.invite" # Owner only
SETTINGS_EDIT = "settings.edit" # Owner/Manager only
```
### 5. ✅ Add Permission Checking Dependencies
**Create FastAPI dependencies:**
- `require_vendor_permission(permission)` - Single permission
- `require_vendor_owner()` - Owner only
- `require_any_vendor_permission(*permissions)` - Any of list
- `require_all_vendor_permissions(*permissions)` - All of list
- `get_user_permissions()` - Get user's permission list
**Usage in Routes:**
```python
@router.post("/products")
def create_product(
user: User = Depends(require_vendor_permission("products.create"))
):
# User verified to have products.create permission
...
```
### 6. ✅ Create Vendor Team Service
**Service Responsibilities:**
- `invite_team_member()` - Send invitations
- `accept_invitation()` - Activate accounts
- `remove_team_member()` - Deactivate members
- `update_member_role()` - Change permissions
- `get_team_members()` - List team
**Why Service Layer:**
- Business logic separate from routes
- Reusable across different contexts
- Easier to test
- Consistent error handling
### 7. ✅ Add Helper Methods to Models
**User Model:**
```python
@property
def is_admin(self) -> bool
def is_owner_of(self, vendor_id: int) -> bool
def is_member_of(self, vendor_id: int) -> bool
def get_vendor_role(self, vendor_id: int) -> str
def has_vendor_permission(self, vendor_id: int, permission: str) -> bool
```
**VendorUser Model:**
```python
@property
def is_owner(self) -> bool
def has_permission(self, permission: str) -> bool
def get_all_permissions(self) -> list
```
### 8. ✅ Enhanced Exception Handling
**Add Vendor-Specific Exceptions:**
- `VendorAccessDeniedException` - No access to vendor
- `InsufficientVendorPermissionsException` - Missing permission
- `VendorOwnerOnlyException` - Owner-only operation
- `CannotRemoveVendorOwnerException` - Prevent owner removal
- `InvalidInvitationTokenException` - Bad invitation
- `MaxTeamMembersReachedException` - Team size limit
## Architecture Overview
```
┌─────────────────────────────────────────────────────────┐
│ Platform Level │
│ User.role: "admin" or "vendor" │
│ - Admins: Full platform access │
│ - Vendors: Access to their vendor(s) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Vendor Level │
│ VendorUser.user_type: "owner" or "member" │
│ - Owners: Full vendor access (all permissions) │
│ - Members: Role-based access (limited permissions) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Role Level │
│ Role.permissions: ["products.view", ...] │
│ - Presets: Manager, Staff, Support, Viewer, Marketing │
│ - Custom: Owner can define custom roles │
└─────────────────────────────────────────────────────────┘
```
## Permission Hierarchy
```
Owner (VendorUser.user_type = "owner")
└─> ALL permissions automatically
Team Member (VendorUser.user_type = "member")
└─> Role.permissions (from VendorUser.role_id)
├─> Manager: Most permissions except critical settings/team
├─> Staff: Products, orders, customers (read/write)
├─> Support: Orders, customers (support focus)
├─> Viewer: Read-only access
└─> Custom: Owner-defined permission sets
```
## Security Best Practices
### 1. **Cookie Path Isolation** (Already Implemented ✓)
- Admin: `/admin` path
- Vendor: `/vendor` path
- Customer: `/shop` path
- Prevents cross-context cookie leakage
### 2. **Role Checking at Route Level**
```python
# Good - Check at route definition
@router.post("/products")
def create_product(
user: User = Depends(require_vendor_permission("products.create"))
):
...
# Bad - Check inside handler
@router.post("/products")
def create_product(user: User):
if not has_permission(...): # Don't do this
raise Exception()
```
### 3. **Block Admin from Vendor Routes**
```python
# In vendor auth endpoints
if user.role == "admin":
raise InvalidCredentialsException(
"Admins cannot access vendor portal"
)
```
### 4. **Owner Cannot Be Removed**
```python
if vendor_user.is_owner:
raise CannotRemoveVendorOwnerException(vendor.vendor_code)
```
### 5. **Inactive Users Have No Permissions**
```python
if not user.is_active or not vendor_user.is_active:
return False # No permissions
```
## Frontend Integration
### 1. **Get User Permissions on Load**
```javascript
// On vendor dashboard load
const response = await fetch('/api/v1/vendor/team/me/permissions');
const { permissions } = await response.json();
// Store in state
localStorage.setItem('permissions', JSON.stringify(permissions));
```
### 2. **Show/Hide UI Elements**
```javascript
// Check permission before rendering
function canViewProducts() {
const permissions = JSON.parse(localStorage.getItem('permissions'));
return permissions.includes('products.view');
}
// In template
{canViewProducts() && <ProductsList />}
```
### 3. **Disable Actions Without Permission**
```javascript
<button
disabled={!permissions.includes('products.delete')}
onClick={handleDelete}
>
Delete Product
</button>
```
## Database Schema Summary
```
users
├── id (PK)
├── email (unique)
├── username (unique)
├── role ('admin' | 'vendor') ← Platform role only
├── is_active
└── is_email_verified ← New field
vendors
├── id (PK)
├── vendor_code (unique)
├── owner_user_id (FK → users.id)
└── ...
vendor_users (junction table with role info)
├── id (PK)
├── vendor_id (FK → vendors.id)
├── user_id (FK → users.id)
├── user_type ('owner' | 'member') ← New field
├── role_id (FK → roles.id, nullable) ← NULL for owners
├── invitation_token ← New field
├── invitation_sent_at ← New field
├── invitation_accepted_at ← New field
└── is_active
roles (vendor-specific)
├── id (PK)
├── vendor_id (FK → vendors.id)
├── name (e.g., 'Manager', 'Staff')
└── permissions (JSON: ["products.view", ...])
customers (separate, vendor-scoped)
├── id (PK)
├── vendor_id (FK → vendors.id)
├── email (unique within vendor)
└── ... (separate auth from User)
```
## Implementation Checklist
### Phase 1: Database & Models
- [ ] Add `is_email_verified` to User model
- [ ] Update User.role to only accept 'admin'/'vendor'
- [ ] Add `user_type` to VendorUser model
- [ ] Add invitation fields to VendorUser
- [ ] Make VendorUser.role_id nullable
- [ ] Create permissions.py with constants
- [ ] Add helper methods to User model
- [ ] Add helper methods to VendorUser model
- [ ] Run database migration
### Phase 2: Services & Logic
- [ ] Create vendor_team_service.py
- [ ] Implement invite_team_member()
- [ ] Implement accept_invitation()
- [ ] Implement remove_team_member()
- [ ] Implement update_member_role()
- [ ] Add vendor-specific exceptions
- [ ] Set up email sending for invitations
### Phase 3: API & Dependencies
- [ ] Add permission checking dependencies to deps.py
- [ ] Update vendor auth endpoint to block admins
- [ ] Create team management routes
- [ ] Add permission checks to existing routes
- [ ] Create invitation acceptance endpoint (public)
- [ ] Add /me/permissions endpoint
### Phase 4: Testing
- [ ] Test owner has all permissions
- [ ] Test team members respect role permissions
- [ ] Test invitation flow end-to-end
- [ ] Test cannot remove owner
- [ ] Test admin blocked from vendor routes
- [ ] Test permission checking in all routes
- [ ] Test inactive users have no access
### Phase 5: Frontend
- [ ] Load user permissions on login
- [ ] Show/hide UI based on permissions
- [ ] Implement invitation acceptance page
- [ ] Add team management UI (owner only)
- [ ] Add role selector when inviting
- [ ] Show permission lists for roles
## Migration Steps
1. **Backup database**
2. **Run Alembic migration** (see migration guide)
3. **Update User roles** (convert 'user' to 'vendor')
4. **Set vendor owners** in vendor_users
5. **Create default roles** for all vendors
6. **Assign roles** to existing team members
7. **Verify migration** with SQL queries
8. **Test system** thoroughly
9. **Deploy** to production
## Common Pitfalls to Avoid
### ❌ Don't: Mix Platform and Vendor Roles
```python
# Bad
user.role = "manager" # Vendor role in User table
```
```python
# Good
user.role = "vendor" # Platform role
vendor_user.role.name = "manager" # Vendor role
```
### ❌ Don't: Check Permissions in Business Logic
```python
# Bad
def create_product(user, product_data):
if not has_permission(user, "products.create"):
raise Exception()
# ... create product
```
```python
# Good
@router.post("/products")
def create_product(
product_data: ProductCreate,
user: User = Depends(require_vendor_permission("products.create"))
):
# Permission already checked by dependency
return service.create_product(user, product_data)
```
### ❌ Don't: Allow Owner Removal
```python
# Bad
def remove_team_member(vendor, user_id):
vendor_user = get_vendor_user(vendor, user_id)
vendor_user.is_active = False
```
```python
# Good
def remove_team_member(vendor, user_id):
vendor_user = get_vendor_user(vendor, user_id)
if vendor_user.is_owner:
raise CannotRemoveVendorOwnerException(vendor.vendor_code)
vendor_user.is_active = False
```
### ❌ Don't: Forget to Check is_active
```python
# Bad
def has_permission(user, vendor_id, permission):
return permission in user.get_vendor_role(vendor_id).permissions
```
```python
# Good
def has_permission(user, vendor_id, permission):
if not user.is_active:
return False
vendor_user = get_vendor_user(user, vendor_id)
if not vendor_user or not vendor_user.is_active:
return False
return vendor_user.has_permission(permission)
```
## Questions & Answers
**Q: Can a user be a team member of multiple vendors?**
A: Yes! A user can have multiple VendorUser entries, one per vendor. Each has independent role/permissions.
**Q: Can a vendor have multiple owners?**
A: No. Each vendor has one owner (Vendor.owner_user_id). The owner can delegate management via Manager role, but there's only one true owner.
**Q: What happens to team members when owner changes?**
A: Team members remain. Update Vendor.owner_user_id and both old/new owner's VendorUser.user_type.
**Q: Can admins access vendor dashboards?**
A: No. Admins are blocked from vendor routes. This is intentional security.
**Q: How do customers authenticate?**
A: Customers use the Customer model with separate authentication. They're vendor-scoped and can't access vendor/admin areas.
**Q: Can permissions be customized per team member?**
A: Yes! When inviting, pass `custom_permissions` array to override role presets.
**Q: How do invitation links work?**
A: Invitation token is a secure random string stored in VendorUser. Link format: `/vendor/invitation/accept?token=<token>`. Token is single-use and expires in 7 days.
## Next Steps
1. Review all generated files in `/home/claude/`
2. Integrate changes into your codebase
3. Create and run database migration
4. Update existing routes with permission checks
5. Implement team management UI
6. Test thoroughly in development
7. Deploy to staging for user acceptance testing
8. Deploy to production with monitoring
## Support
All implementation files have been created in `/home/claude/`:
- `user_model_improved.py` - Updated User model
- `vendor_user_improved.py` - Updated VendorUser model
- `permissions.py` - Permission system
- `vendor_exceptions.py` - Vendor exceptions
- `deps_permissions.py` - Permission dependencies
- `vendor_team_service.py` - Team management service
- `team_routes_example.py` - Example routes
- `rbac_migration_guide.md` - Database migration guide
These are ready to be integrated into your project structure.

View File

@@ -1,916 +0,0 @@
# Vendor & Users Pages Migration Plan - FINAL
## Based on Actual Legacy Files
**Date:** October 23, 2025
**Status:** Icons fixed ✅ | Logout working ✅ | Dashboard migrated ✅
---
## 📁 Legacy Files Analysis
### Existing Files:
1. **vendors.html** - Basic placeholder (needs vendor LIST implementation)
2. **vendor-edit.html** - Detailed edit form (OLD CSS, needs redesign)
3. **vendors.js** - Only has `vendorCreation()` function (NOT vendor list)
4. **vendor-edit.js** - Complete edit functionality (uses OLD auth/api pattern)
5. **init-alpine.js** - Base Alpine data (theme, menus)
### Key Findings:
-`vendors.js` currently only has CREATE vendor function
- ❌ NO vendor LIST function exists yet
-`vendor-edit.js` exists but uses OLD patterns:
- Uses `apiClient` (should use `ApiClient`)
- Uses `Auth.isAuthenticated()` pattern
- Uses `Utils.confirm()` and custom modals
- ✅ Dashboard pattern: Uses `ApiClient`, `Logger`, `Utils.showToast`
### Pattern Differences:
**OLD Pattern (vendor-edit.js):**
```javascript
// OLD API client (lowercase)
const response = await apiClient.get('/admin/vendors/1');
// OLD Auth pattern
if (!Auth.isAuthenticated()) { ... }
const user = Auth.getCurrentUser();
```
**NEW Pattern (dashboard.js):**
```javascript
// NEW API client (uppercase)
const response = await ApiClient.get('/admin/vendors');
// NEW Auth - handled by cookie, no client-side check needed
// Just call API and let middleware handle it
```
---
## 🎯 Migration Strategy
### Task 1: Create Vendor List Function (HIGH PRIORITY) 🏪
**Current State:** vendors.js only has `vendorCreation()` function
**Goal:** Add `adminVendors()` function for the vendor LIST page
#### 1.1 Update vendors.js - Add Vendor List Function
**File:** `static/admin/js/vendors.js`
**Action:** ADD new function (keep existing `vendorCreation()` function):
```javascript
// static/admin/js/vendors.js
// ============================================
// VENDOR LIST FUNCTION (NEW - Add this)
// ============================================
function adminVendors() {
return {
// State
vendors: [],
stats: {
total: 0,
verified: 0,
pending: 0,
inactive: 0
},
loading: false,
error: null,
// Initialize
async init() {
Logger.info('Vendors page initialized', 'VENDORS');
await this.loadVendors();
await this.loadStats();
},
// Load vendors list
async loadVendors() {
this.loading = true;
this.error = null;
try {
const response = await ApiClient.get('/admin/vendors');
// Handle different response structures
this.vendors = response.vendors || response.items || response || [];
Logger.info('Vendors loaded', 'VENDORS', { count: this.vendors.length });
} catch (error) {
Logger.error('Failed to load vendors', 'VENDORS', error);
this.error = error.message || 'Failed to load vendors';
Utils.showToast('Failed to load vendors', 'error');
} finally {
this.loading = false;
}
},
// Load statistics
async loadStats() {
try {
const response = await ApiClient.get('/admin/vendors/stats');
this.stats = response;
Logger.info('Stats loaded', 'VENDORS', this.stats);
} catch (error) {
Logger.error('Failed to load stats', 'VENDORS', error);
// Don't show error toast for stats, just log it
}
},
// Format date (matches dashboard pattern)
formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
},
// View vendor details
viewVendor(vendorCode) {
Logger.info('View vendor', 'VENDORS', { vendorCode });
// Navigate to details page or open modal
window.location.href = `/admin/vendors/${vendorCode}`;
},
// Edit vendor
editVendor(vendorCode) {
Logger.info('Edit vendor', 'VENDORS', { vendorCode });
window.location.href = `/admin/vendors/${vendorCode}/edit`;
},
// Delete vendor
async deleteVendor(vendor) {
if (!confirm(`Are you sure you want to delete vendor "${vendor.name}"?\n\nThis action cannot be undone.`)) {
return;
}
try {
await ApiClient.delete(`/admin/vendors/${vendor.vendor_code}`);
Utils.showToast('Vendor deleted successfully', 'success');
await this.loadVendors();
await this.loadStats();
} catch (error) {
Logger.error('Failed to delete vendor', 'VENDORS', error);
Utils.showToast(error.message || 'Failed to delete vendor', 'error');
}
},
// Open create modal/page
openCreateModal() {
Logger.info('Open create vendor', 'VENDORS');
// Navigate to create page (or open modal)
window.location.href = '/admin/vendors/create';
}
};
}
// ============================================
// VENDOR CREATION FUNCTION (EXISTING - Keep this)
// ============================================
function vendorCreation() {
// ... keep your existing vendorCreation function as is ...
// (the code you already have)
}
```
#### 1.2 Create Vendors List Template
**File:** `app/templates/admin/vendors.html`
```jinja2
{# app/templates/admin/vendors.html #}
{% extends "admin/base.html" %}
{% block title %}Vendors{% endblock %}
{% block alpine_data %}adminVendors(){% endblock %}
{% block content %}
<!-- Page Header -->
<div class="flex items-center justify-between my-6">
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Vendor Management
</h2>
<a
href="/admin/vendors/create"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"
>
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
Create Vendor
</a>
</div>
<!-- Loading State -->
<div x-show="loading" class="text-center py-12">
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading vendors...</p>
</div>
<!-- Error State -->
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
<div>
<p class="font-semibold">Error loading vendors</p>
<p class="text-sm" x-text="error"></p>
</div>
</div>
<!-- Stats Cards - EXACTLY like dashboard -->
<div x-show="!loading" class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
<!-- Card: Total Vendors -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full dark:text-orange-100 dark:bg-orange-500">
<span x-html="$icon('user-group', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
Total Vendors
</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total || 0">
0
</p>
</div>
</div>
<!-- Card: Verified Vendors -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
<span x-html="$icon('badge-check', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
Verified Vendors
</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.verified || 0">
0
</p>
</div>
</div>
<!-- Card: Pending Verification -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-yellow-500 bg-yellow-100 rounded-full dark:text-yellow-100 dark:bg-yellow-500">
<span x-html="$icon('clock', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
Pending
</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.pending || 0">
0
</p>
</div>
</div>
<!-- Card: Inactive Vendors -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-red-500 bg-red-100 rounded-full dark:text-red-100 dark:bg-red-500">
<span x-html="$icon('x-circle', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
Inactive
</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.inactive || 0">
0
</p>
</div>
</div>
</div>
<!-- Vendors Table - EXACTLY like dashboard table -->
<div x-show="!loading" class="w-full overflow-hidden rounded-lg shadow-xs">
<div class="w-full overflow-x-auto">
<table class="w-full whitespace-no-wrap">
<thead>
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
<th class="px-4 py-3">Vendor</th>
<th class="px-4 py-3">Subdomain</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Created</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
<!-- Empty State -->
<template x-if="vendors.length === 0">
<tr>
<td colspan="5" class="px-4 py-8 text-center text-gray-600 dark:text-gray-400">
<div class="flex flex-col items-center">
<span x-html="$icon('user-group', 'w-12 h-12 mb-2 text-gray-300')"></span>
<p class="font-medium">No vendors found</p>
<p class="text-xs mt-1">Create your first vendor to get started</p>
</div>
</td>
</tr>
</template>
<!-- Vendor Rows -->
<template x-for="vendor in vendors" :key="vendor.id || vendor.vendor_code">
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<!-- Vendor Info with Avatar -->
<td class="px-4 py-3">
<div class="flex items-center text-sm">
<div class="relative hidden w-8 h-8 mr-3 rounded-full md:block">
<div class="absolute inset-0 rounded-full bg-purple-100 dark:bg-purple-600 flex items-center justify-center">
<span class="text-xs font-semibold text-purple-600 dark:text-purple-100"
x-text="vendor.name?.charAt(0).toUpperCase() || '?'"></span>
</div>
</div>
<div>
<p class="font-semibold" x-text="vendor.name"></p>
<p class="text-xs text-gray-600 dark:text-gray-400" x-text="vendor.vendor_code"></p>
</div>
</div>
</td>
<!-- Subdomain -->
<td class="px-4 py-3 text-sm" x-text="vendor.subdomain"></td>
<!-- Status Badge -->
<td class="px-4 py-3 text-xs">
<span class="inline-flex items-center px-2 py-1 font-semibold leading-tight rounded-full"
:class="vendor.is_verified ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-orange-700 bg-orange-100 dark:text-white dark:bg-orange-600'">
<span x-show="vendor.is_verified" x-html="$icon('badge-check', 'w-3 h-3 mr-1')"></span>
<span x-text="vendor.is_verified ? 'Verified' : 'Pending'"></span>
</span>
</td>
<!-- Created Date -->
<td class="px-4 py-3 text-sm" x-text="formatDate(vendor.created_at)"></td>
<!-- Actions -->
<td class="px-4 py-3">
<div class="flex items-center space-x-2 text-sm">
<!-- View Button -->
<button
@click="viewVendor(vendor.vendor_code)"
class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
title="View details"
>
<span x-html="$icon('eye', 'w-5 h-5')"></span>
</button>
<!-- Edit Button -->
<button
@click="editVendor(vendor.vendor_code)"
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
title="Edit vendor"
>
<span x-html="$icon('pencil', 'w-5 h-5')"></span>
</button>
<!-- Delete Button -->
<button
@click="deleteVendor(vendor)"
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
title="Delete vendor"
>
<span x-html="$icon('trash', 'w-5 h-5')"></span>
</button>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/vendors.js') }}"></script>
{% endblock %}
```
**Checklist Task 1:**
- [ ] Add `adminVendors()` function to `static/admin/js/vendors.js` (keep existing `vendorCreation()`)
- [ ] Create `app/templates/admin/vendors.html` template
- [ ] Add backend route for `/admin/vendors`
- [ ] Test vendor list page loads
- [ ] Test stats cards display
- [ ] Test vendor table displays data
- [ ] Test action buttons work
---
### Task 2: Migrate Vendor Edit Page (HIGH PRIORITY) ✏️
**Current State:** vendor-edit.js exists but uses OLD patterns
**Goal:** Update to use NEW patterns (ApiClient, Logger, no Auth checks)
#### 2.1 Update vendor-edit.js to NEW Pattern
**File:** `static/admin/js/vendor-edit.js`
**Changes Needed:**
1. Replace `apiClient``ApiClient`
2. Remove `Auth.isAuthenticated()` checks (handled by backend)
3. Replace `Utils.confirm()` → Use `confirm()`
4. Remove custom modals, use simple confirms
5. Add Logger calls
6. Update route patterns
```javascript
// static/admin/js/vendor-edit.js
function adminVendorEdit() {
return {
// State
vendor: null,
formData: {},
errors: {},
loadingVendor: false,
saving: false,
vendorCode: null,
// Initialize
async init() {
Logger.info('Vendor edit page initialized', 'VENDOR_EDIT');
// Get vendor code from URL
const path = window.location.pathname;
const match = path.match(/\/admin\/vendors\/([^\/]+)\/edit/);
if (match) {
this.vendorCode = match[1];
Logger.info('Editing vendor', 'VENDOR_EDIT', { vendorCode: this.vendorCode });
await this.loadVendor();
} else {
Logger.error('No vendor code in URL', 'VENDOR_EDIT');
Utils.showToast('Invalid vendor URL', 'error');
setTimeout(() => window.location.href = '/admin/vendors', 2000);
}
},
// Load vendor data
async loadVendor() {
this.loadingVendor = true;
try {
// CHANGED: apiClient → ApiClient
const response = await ApiClient.get(`/admin/vendors/${this.vendorCode}`);
this.vendor = response;
// Initialize form data
this.formData = {
name: response.name || '',
subdomain: response.subdomain || '',
description: response.description || '',
contact_email: response.contact_email || '',
contact_phone: response.contact_phone || '',
website: response.website || '',
business_address: response.business_address || '',
tax_number: response.tax_number || ''
};
Logger.info('Vendor loaded', 'VENDOR_EDIT', {
vendor_code: this.vendor.vendor_code,
name: this.vendor.name
});
} catch (error) {
Logger.error('Failed to load vendor', 'VENDOR_EDIT', error);
Utils.showToast('Failed to load vendor', 'error');
setTimeout(() => window.location.href = '/admin/vendors', 2000);
} finally {
this.loadingVendor = false;
}
},
// Format subdomain
formatSubdomain() {
this.formData.subdomain = this.formData.subdomain
.toLowerCase()
.replace(/[^a-z0-9-]/g, '');
},
// Submit form
async handleSubmit() {
Logger.info('Submitting vendor update', 'VENDOR_EDIT');
this.errors = {};
this.saving = true;
try {
// CHANGED: apiClient → ApiClient
const response = await ApiClient.put(
`/admin/vendors/${this.vendorCode}`,
this.formData
);
this.vendor = response;
Utils.showToast('Vendor updated successfully', 'success');
Logger.info('Vendor updated', 'VENDOR_EDIT', response);
// Optionally redirect back to list
// setTimeout(() => window.location.href = '/admin/vendors', 1500);
} catch (error) {
Logger.error('Failed to update vendor', 'VENDOR_EDIT', error);
// Handle validation errors
if (error.details && error.details.validation_errors) {
error.details.validation_errors.forEach(err => {
const field = err.loc?.[1] || err.loc?.[0];
if (field) {
this.errors[field] = err.msg;
}
});
}
Utils.showToast(error.message || 'Failed to update vendor', 'error');
} finally {
this.saving = false;
}
},
// Toggle verification
async toggleVerification() {
const action = this.vendor.is_verified ? 'unverify' : 'verify';
// CHANGED: Simple confirm instead of custom modal
if (!confirm(`Are you sure you want to ${action} this vendor?`)) {
return;
}
this.saving = true;
try {
// CHANGED: apiClient → ApiClient
const response = await ApiClient.put(
`/admin/vendors/${this.vendorCode}/verification`,
{ is_verified: !this.vendor.is_verified }
);
this.vendor = response;
Utils.showToast(`Vendor ${action}ed successfully`, 'success');
Logger.info(`Vendor ${action}ed`, 'VENDOR_EDIT');
} catch (error) {
Logger.error(`Failed to ${action} vendor`, 'VENDOR_EDIT', error);
Utils.showToast(`Failed to ${action} vendor`, 'error');
} finally {
this.saving = false;
}
},
// Toggle active status
async toggleActive() {
const action = this.vendor.is_active ? 'deactivate' : 'activate';
// CHANGED: Simple confirm instead of custom modal
if (!confirm(`Are you sure you want to ${action} this vendor?\n\nThis will affect their operations.`)) {
return;
}
this.saving = true;
try {
// CHANGED: apiClient → ApiClient
const response = await ApiClient.put(
`/admin/vendors/${this.vendorCode}/status`,
{ is_active: !this.vendor.is_active }
);
this.vendor = response;
Utils.showToast(`Vendor ${action}d successfully`, 'success');
Logger.info(`Vendor ${action}d`, 'VENDOR_EDIT');
} catch (error) {
Logger.error(`Failed to ${action} vendor`, 'VENDOR_EDIT', error);
Utils.showToast(`Failed to ${action} vendor`, 'error');
} finally {
this.saving = false;
}
}
};
}
```
#### 2.2 Create Vendor Edit Template
**File:** `app/templates/admin/vendor-edit.html`
```jinja2
{# app/templates/admin/vendor-edit.html #}
{% extends "admin/base.html" %}
{% block title %}Edit Vendor{% endblock %}
{% block alpine_data %}adminVendorEdit(){% endblock %}
{% block content %}
<!-- Page Header -->
<div class="flex items-center justify-between my-6">
<div>
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Edit Vendor
</h2>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1" x-show="vendor">
<span x-text="vendor?.name"></span>
<span class="text-gray-400">•</span>
<span x-text="vendor?.vendor_code"></span>
</p>
</div>
<a href="/admin/vendors"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800 hover:border-gray-400 focus:outline-none">
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
Back to Vendors
</a>
</div>
<!-- Loading State -->
<div x-show="loadingVendor" class="text-center py-12">
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading vendor...</p>
</div>
<!-- Edit Form -->
<div x-show="!loadingVendor && vendor" class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
<!-- Quick Actions -->
<div class="flex items-center gap-3 mb-6 pb-6 border-b dark:border-gray-700">
<button
@click="toggleVerification()"
:disabled="saving"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 rounded-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
:class="vendor?.is_verified ? 'bg-orange-600 hover:bg-orange-700' : 'bg-green-600 hover:bg-green-700'">
<span x-html="$icon(vendor?.is_verified ? 'x-circle' : 'badge-check', 'w-4 h-4 mr-2')"></span>
<span x-text="vendor?.is_verified ? 'Unverify Vendor' : 'Verify Vendor'"></span>
</button>
<button
@click="toggleActive()"
:disabled="saving"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 rounded-lg focus:outline-none focus:shadow-outline-purple disabled:opacity-50"
:class="vendor?.is_active ? 'bg-red-600 hover:bg-red-700' : 'bg-green-600 hover:bg-green-700'">
<span x-html="$icon(vendor?.is_active ? 'lock-closed' : 'lock-open', 'w-4 h-4 mr-2')"></span>
<span x-text="vendor?.is_active ? 'Deactivate' : 'Activate'"></span>
</button>
</div>
<!-- Form -->
<form @submit.prevent="handleSubmit">
<div class="grid gap-6 mb-8 md:grid-cols-2">
<!-- Left Column: Basic Info -->
<div>
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
Basic Information
</h3>
<!-- Vendor Code (readonly) -->
<label class="block mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Vendor Code
</span>
<input
type="text"
x-model="vendor.vendor_code"
disabled
class="block w-full mt-1 text-sm bg-gray-100 border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 cursor-not-allowed"
>
<span class="text-xs text-gray-600 dark:text-gray-400 mt-1">
Cannot be changed after creation
</span>
</label>
<!-- Name -->
<label class="block mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Vendor Name <span class="text-red-600">*</span>
</span>
<input
type="text"
x-model="formData.name"
required
maxlength="255"
:disabled="saving"
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
:class="{ 'border-red-600': errors.name }"
>
<span x-show="errors.name" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.name"></span>
</label>
<!-- Subdomain -->
<label class="block mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Subdomain <span class="text-red-600">*</span>
</span>
<input
type="text"
x-model="formData.subdomain"
@input="formatSubdomain()"
required
maxlength="100"
:disabled="saving"
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
:class="{ 'border-red-600': errors.subdomain }"
>
<span class="text-xs text-gray-600 dark:text-gray-400 mt-1">
Lowercase letters, numbers, and hyphens only
</span>
<span x-show="errors.subdomain" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.subdomain"></span>
</label>
<!-- Description -->
<label class="block mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Description
</span>
<textarea
x-model="formData.description"
rows="3"
:disabled="saving"
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-textarea"
></textarea>
</label>
</div>
<!-- Right Column: Contact Info -->
<div>
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
Contact Information
</h3>
<!-- Owner Email (readonly) -->
<label class="block mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Owner Email
</span>
<input
type="email"
x-model="vendor.owner_email"
disabled
class="block w-full mt-1 text-sm bg-gray-100 border-gray-300 rounded-md dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 cursor-not-allowed"
>
</label>
<!-- Contact Email -->
<label class="block mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Contact Email <span class="text-red-600">*</span>
</span>
<input
type="email"
x-model="formData.contact_email"
required
:disabled="saving"
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
:class="{ 'border-red-600': errors.contact_email }"
>
<span x-show="errors.contact_email" class="text-xs text-red-600 dark:text-red-400 mt-1" x-text="errors.contact_email"></span>
</label>
<!-- Phone -->
<label class="block mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Phone
</span>
<input
type="tel"
x-model="formData.contact_phone"
:disabled="saving"
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
>
</label>
<!-- Website -->
<label class="block mb-4">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Website
</span>
<input
type="url"
x-model="formData.website"
:disabled="saving"
placeholder="https://example.com"
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
>
</label>
</div>
</div>
<!-- Business Details -->
<div class="mb-8">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
Business Details
</h3>
<div class="grid gap-6 md:grid-cols-2">
<!-- Business Address -->
<label class="block">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Business Address
</span>
<textarea
x-model="formData.business_address"
rows="3"
:disabled="saving"
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-textarea"
></textarea>
</label>
<!-- Tax Number -->
<label class="block">
<span class="text-sm font-medium text-gray-700 dark:text-gray-400">
Tax Number
</span>
<input
type="text"
x-model="formData.tax_number"
:disabled="saving"
class="block w-full mt-1 text-sm border-gray-300 rounded-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray form-input"
>
</label>
</div>
</div>
<!-- Save Button -->
<div class="flex items-center justify-end gap-3">
<a
href="/admin/vendors"
class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800 hover:border-gray-400 focus:outline-none">
Cancel
</a>
<button
type="submit"
:disabled="saving"
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed">
<span x-show="!saving">Save Changes</span>
<span x-show="saving" class="flex items-center">
<span x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
Saving...
</span>
</button>
</div>
</form>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/vendor-edit.js') }}"></script>
{% endblock %}
```
**Checklist Task 2:**
- [ ] Update `static/admin/js/vendor-edit.js` with NEW patterns
- [ ] Create `app/templates/admin/vendor-edit.html` template
- [ ] Add backend route for `/admin/vendors/{vendor_code}/edit`
- [ ] Test vendor edit page loads
- [ ] Test form submission works
- [ ] Test quick action buttons (verify/activate)
- [ ] Test validation error display
---
### Task 3: Create Users Page (MEDIUM PRIORITY) 👥
**File:** `app/templates/admin/users.html`
**File:** `static/admin/js/users.js`
Create from scratch - same pattern as vendors list page (see previous plan sections for full code)
---
## ⏰ Updated Time Estimates
| Task | Estimated Time | Priority |
|------|---------------|----------|
| Task 1: Add Vendor List Function | 45-60 min | HIGH |
| Task 2: Update Vendor Edit to NEW pattern | 60-75 min | HIGH |
| Task 3: Create Users Page | 45-60 min | MEDIUM |
| Testing & Verification | 30-45 min | HIGH |
| Cleanup | 15 min | LOW |
**Total: 3-4 hours**
---
## ✅ Success Criteria
By end of session:
- [ ] Vendor LIST page working with stats and table
- [ ] Vendor EDIT page using NEW patterns (ApiClient, Logger)
- [ ] Users page created and working
- [ ] All pages match dashboard styling
- [ ] No console errors
- [ ] Old patterns removed from vendor-edit.js
---
## 📝 Key Pattern Changes
### OLD Pattern (vendor-edit.js):
```javascript
// OLD
const vendor = await apiClient.get('/admin/vendors/1');
if (!Auth.isAuthenticated()) { ... }
Utils.confirm('Message', 'Title');
```
### NEW Pattern (dashboard.js):
```javascript
// NEW
const vendor = await ApiClient.get('/admin/vendors/1');
// No Auth checks - backend handles it
confirm('Message with details');
Logger.info('Action', 'COMPONENT', data);
```
---
**Let's migrate! 🚀**

View File

@@ -1,752 +0,0 @@
# Frontend Documentation Plan - LetzShop Platform
## 📚 Documentation Structure Overview
This documentation plan focuses on **practical guides** for implementing new features in the three main sections of the platform: **Admin**, **Vendor**, and **Shop** (customer-facing).
---
## 🎯 Documentation Goals
1. **Enable rapid development** - Team members can implement new pages in 15-30 minutes
2. **Ensure consistency** - All pages follow the same Alpine.js architecture patterns
3. **Reduce errors** - Common pitfalls are documented and preventable
4. **Facilitate onboarding** - New developers can contribute within days, not weeks
5. **Maintain quality** - Code quality standards are embedded in the guides
---
## 📖 Proposed Documentation Structure
```
docs/
├── frontend/
│ ├── index.md # Frontend overview & navigation
│ │
│ ├── 01-getting-started/
│ │ ├── overview.md # Tech stack & architecture summary
│ │ ├── setup.md # Local development setup
│ │ ├── project-structure.md # File organization
│ │ └── naming-conventions.md # Naming standards
│ │
│ ├── 02-core-concepts/
│ │ ├── alpine-architecture.md # Alpine.js patterns we use
│ │ ├── template-system.md # Jinja2 templates & inheritance
│ │ ├── state-management.md # Reactive state with Alpine.js
│ │ ├── api-integration.md # Using apiClient
│ │ └── common-pitfalls.md # Variable conflicts & other issues
│ │
│ ├── 03-implementation-guides/
│ │ ├── admin-section/
│ │ │ ├── creating-admin-page.md # Step-by-step admin page guide
│ │ │ ├── admin-page-template.md # Copy-paste template
│ │ │ ├── sidebar-integration.md # Adding menu items
│ │ │ └── admin-examples.md # Real examples (dashboard, vendors)
│ │ │
│ │ ├── vendor-section/
│ │ │ ├── creating-vendor-page.md # Step-by-step vendor page guide
│ │ │ ├── vendor-page-template.md # Copy-paste template
│ │ │ └── vendor-examples.md # Real examples
│ │ │
│ │ └── shop-section/
│ │ ├── creating-shop-page.md # Step-by-step shop page guide
│ │ ├── shop-page-template.md # Copy-paste template
│ │ └── shop-examples.md # Real examples
│ │
│ ├── 04-ui-components/
│ │ ├── component-library.md # Overview & reference
│ │ ├── forms.md # All form components
│ │ ├── buttons.md # Button styles
│ │ ├── cards.md # Card components
│ │ ├── tables.md # Table patterns
│ │ ├── modals.md # Modal dialogs
│ │ ├── badges.md # Status badges
│ │ ├── alerts-toasts.md # Notifications
│ │ └── icons.md # Icon usage
│ │
│ ├── 05-common-patterns/
│ │ ├── data-loading.md # Loading states & error handling
│ │ ├── pagination.md # Client-side pagination
│ │ ├── filtering-sorting.md # Table operations
│ │ ├── form-validation.md # Validation patterns
│ │ ├── crud-operations.md # Create, Read, Update, Delete
│ │ └── real-time-updates.md # WebSocket/polling patterns
│ │
│ ├── 06-advanced-topics/
│ │ ├── performance.md # Optimization techniques
│ │ ├── dark-mode.md # Theme implementation
│ │ ├── accessibility.md # A11y guidelines
│ │ ├── responsive-design.md # Mobile-first approach
│ │ └── debugging.md # Debugging techniques
│ │
│ ├── 07-testing/
│ │ ├── testing-overview.md # Testing strategy
│ │ ├── testing-hub-guide.md # Using the testing hub
│ │ └── manual-testing-checklist.md # QA checklist
│ │
│ └── 08-reference/
│ ├── alpine-js-reference.md # Alpine.js quick reference
│ ├── api-client-reference.md # apiClient methods
│ ├── utils-reference.md # Utility functions
│ ├── css-classes.md # Tailwind classes we use
│ └── troubleshooting.md # Common issues & solutions
└── backend/ # Backend docs (later)
└── ...
```
---
## 📝 Priority Documentation Order
### Phase 1: Core Essentials (Week 1)
**Goal:** Team can create basic pages immediately
1. **`01-getting-started/overview.md`**
- Quick tech stack summary
- Architecture diagram
- 5-minute quick start
2. **`02-core-concepts/alpine-architecture.md`** ⭐ CRITICAL
- The `...data()` pattern
- `currentPage` identifier
- Initialization guards
- Variable naming (avoid conflicts!)
- Based on FRONTEND_ARCHITECTURE_OVERVIEW.txt
3. **`03-implementation-guides/admin-section/creating-admin-page.md`** ⭐ CRITICAL
- Step-by-step guide
- 10-minute implementation time
- Includes sidebar integration
- Based on ALPINE_PAGE_TEMPLATE.md + COMPLETE_IMPLEMENTATION_GUIDE.md
4. **`03-implementation-guides/admin-section/admin-page-template.md`** ⭐ CRITICAL
- Copy-paste HTML template
- Copy-paste JavaScript template
- Includes all essential patterns
### Phase 2: Component Library (Week 2)
**Goal:** Team knows all available UI components
5. **`04-ui-components/component-library.md`**
- Live examples
- Based on Components page we created
6. **`04-ui-components/forms.md`**
- All form inputs
- Validation patterns
- Based on UI_COMPONENTS.md
7. **`04-ui-components/icons.md`**
- How to use icons
- Category reference
- Based on Icons Browser we created
### Phase 3: Common Patterns (Week 3)
**Goal:** Team can implement common features
8. **`05-common-patterns/pagination.md`**
- Based on PAGINATION_DOCUMENTATION.md
- Avoid `currentPage` conflict!
9. **`05-common-patterns/crud-operations.md`**
- List, create, edit, delete patterns
- Based on vendors.js patterns
10. **`05-common-patterns/data-loading.md`**
- Loading states
- Error handling
- Based on dashboard.js patterns
### Phase 4: Other Sections (Week 4)
**Goal:** Vendor and Shop sections documented
11. **`03-implementation-guides/vendor-section/creating-vendor-page.md`**
12. **`03-implementation-guides/shop-section/creating-shop-page.md`**
### Phase 5: Reference & Polish (Week 5)
**Goal:** Complete reference documentation
13. **`02-core-concepts/common-pitfalls.md`** ⭐ IMPORTANT
- Variable name conflicts (currentPage!)
- Based on VENDORS_SIDEBAR_FIX.md
14. **`08-reference/troubleshooting.md`**
15. All remaining reference docs
---
## 🎯 Core Documentation Files (Detailed Specs)
### 1. `02-core-concepts/alpine-architecture.md`
**Content:**
```markdown
# Alpine.js Architecture Pattern
## Overview
Our frontend uses Alpine.js with a specific inheritance pattern...
## The Base Pattern
[Include FRONTEND_ARCHITECTURE_OVERVIEW.txt content]
## File Structure
[Show the correct file locations]
## Critical Rules
1. ALWAYS use `...data()` first
2. ALWAYS set `currentPage` identifier
3. ALWAYS use lowercase `apiClient`
4. ALWAYS include initialization guard
5. ALWAYS use unique variable names
## Common Mistakes
[Include vendor currentPage conflict example]
```
**Based on:**
- FRONTEND_ARCHITECTURE_OVERVIEW.txt
- ALPINE_PAGE_TEMPLATE.md
- VENDORS_SIDEBAR_FIX.md
---
### 2. `03-implementation-guides/admin-section/creating-admin-page.md`
**Content:**
```markdown
# Creating a New Admin Page (10-Minute Guide)
## Prerequisites
- [ ] Backend API endpoint exists
- [ ] Route added to pages.py
- [ ] Sidebar menu item planned
## Step 1: Create HTML Template (3 minutes)
Copy this template to `app/templates/admin/your-page.html`:
[Include complete template from ALPINE_PAGE_TEMPLATE.md]
## Step 2: Create JavaScript Component (5 minutes)
Copy this template to `static/admin/js/your-page.js`:
[Include complete JS template]
## Step 3: Add Sidebar Menu Item (1 minute)
[Show exact HTML to add to sidebar]
## Step 4: Add Route (1 minute)
[Show exact Python code for pages.py]
## Step 5: Test (2 minutes)
Checklist:
- [ ] Page loads
- [ ] Sidebar shows purple bar
- [ ] Data loads from API
- [ ] Dark mode works
## Common Issues
### Issue 1: Sidebar not showing purple bar
**Cause:** Variable name conflict
**Solution:** [Link to common-pitfalls.md]
### Issue 2: Data not loading
**Cause:** API endpoint mismatch
**Solution:** Check apiClient.get() URL
```
**Based on:**
- ALPINE_PAGE_TEMPLATE.md
- COMPLETE_IMPLEMENTATION_GUIDE.md
- All the architecture documents
---
### 3. `05-common-patterns/pagination.md`
**Content:**
```markdown
# Client-Side Pagination Pattern
## Overview
Pagination splits data into pages...
## Quick Start
[Include PAGINATION_QUICK_START.txt content]
## Full Implementation
[Include PAGINATION_DOCUMENTATION.md content]
## ⚠️ CRITICAL: Avoid Variable Conflicts
When implementing pagination, DO NOT name your pagination variable `currentPage`
if your page uses the sidebar!
**Wrong:**
```javascript
currentPage: 'vendors', // For sidebar
currentPage: 1, // For pagination - OVERWRITES ABOVE!
```
**Correct:**
```javascript
currentPage: 'vendors', // For sidebar
page: 1, // For pagination - different name!
```
[Link to VENDORS_SIDEBAR_FIX.md for full explanation]
```
**Based on:**
- PAGINATION_DOCUMENTATION.md
- PAGINATION_QUICK_START.txt
- VENDORS_SIDEBAR_FIX.md
---
### 4. `04-ui-components/component-library.md`
**Content:**
```markdown
# UI Component Library
## Live Reference
Visit `/admin/components` to see all components with live examples.
## Quick Reference
[Include UI_COMPONENTS_QUICK_REFERENCE.md content]
## Detailed Components
[Include UI_COMPONENTS.md content]
## Copy-Paste Examples
Each component includes:
- Visual example
- HTML code
- Alpine.js bindings
- Dark mode support
```
**Based on:**
- UI_COMPONENTS.md
- UI_COMPONENTS_QUICK_REFERENCE.md
- The Components page we created
---
## 🎨 Documentation Features
### 1. **Code Templates**
Every guide includes ready-to-use code templates:
- ✅ Complete, working code
- ✅ Comments explaining each part
- ✅ No placeholders that need changing (except obvious ones like "your-page")
### 2. **Visual Diagrams**
Use ASCII diagrams and flowcharts:
```
User Request → FastAPI Route → Jinja2 Template → HTML + Alpine.js → Browser
```
### 3. **Before/After Examples**
Show incorrect vs correct implementations:
**❌ Wrong:**
```javascript
currentPage: 'vendors',
currentPage: 1 // Conflict!
```
**✅ Correct:**
```javascript
currentPage: 'vendors',
page: 1 // Different name
```
### 4. **Checklists**
Every guide ends with a testing checklist:
- [ ] Page loads
- [ ] Sidebar active indicator works
- [ ] API data loads
- [ ] Pagination works
- [ ] Dark mode works
### 5. **Troubleshooting Sections**
Common issues with solutions:
```
Problem: Sidebar indicator not showing
Solution: Check for variable name conflicts
Reference: docs/frontend/08-reference/troubleshooting.md#sidebar-issues
```
### 6. **Time Estimates**
Each task shows expected completion time:
- Creating admin page: **10 minutes**
- Adding pagination: **5 minutes**
- Adding form validation: **15 minutes**
### 7. **Cross-References**
Heavy linking between related topics:
```
See also:
- [Alpine.js Architecture](../02-core-concepts/alpine-architecture.md)
- [Common Pitfalls](../02-core-concepts/common-pitfalls.md)
- [Pagination Pattern](../05-common-patterns/pagination.md)
```
---
## 📚 Documentation Standards
### Writing Style
- **Practical first** - Show code, then explain
- **Concise** - Get to the point quickly
- **Examples everywhere** - Real code from actual pages
- **Searchable** - Good headings, keywords, tags
### Code Examples
```javascript
// ✅ Good example - complete and working
function adminDashboard() {
return {
...data(),
currentPage: 'dashboard',
stats: {},
async init() {
if (window._dashboardInitialized) return;
window._dashboardInitialized = true;
await this.loadStats();
}
};
}
// ❌ Bad example - incomplete, needs work
function myPage() {
return {
// TODO: Add your code here
};
}
```
### Warning Boxes
Use admonitions for critical information:
```markdown
!!! warning "Critical: Variable Name Conflicts"
Never use `currentPage` for both sidebar identification and pagination!
This will cause the sidebar active indicator to break.
**Solution:** Use `page` or `pageNumber` for pagination.
```
---
## 🔧 Technical Implementation
### MkDocs Configuration Update
Update `mkdocs.yml` to include frontend section:
```yaml
nav:
- Home: index.md
- Getting Started:
- Installation: getting-started/installation.md
- Quick Start: getting-started/quickstart.md
- Database Setup: getting-started/database-setup.md
- Configuration: getting-started/configuration.md
# NEW: Frontend Documentation
- Frontend:
- Overview: frontend/index.md
- Getting Started:
- Overview: frontend/01-getting-started/overview.md
- Setup: frontend/01-getting-started/setup.md
- Project Structure: frontend/01-getting-started/project-structure.md
- Naming Conventions: frontend/01-getting-started/naming-conventions.md
- Core Concepts:
- Alpine.js Architecture: frontend/02-core-concepts/alpine-architecture.md
- Template System: frontend/02-core-concepts/template-system.md
- State Management: frontend/02-core-concepts/state-management.md
- API Integration: frontend/02-core-concepts/api-integration.md
- Common Pitfalls: frontend/02-core-concepts/common-pitfalls.md
- Implementation Guides:
- Admin Section:
- Creating Admin Page: frontend/03-implementation-guides/admin-section/creating-admin-page.md
- Admin Page Template: frontend/03-implementation-guides/admin-section/admin-page-template.md
- Sidebar Integration: frontend/03-implementation-guides/admin-section/sidebar-integration.md
- Examples: frontend/03-implementation-guides/admin-section/admin-examples.md
- Vendor Section:
- Creating Vendor Page: frontend/03-implementation-guides/vendor-section/creating-vendor-page.md
- Vendor Page Template: frontend/03-implementation-guides/vendor-section/vendor-page-template.md
- Examples: frontend/03-implementation-guides/vendor-section/vendor-examples.md
- Shop Section:
- Creating Shop Page: frontend/03-implementation-guides/shop-section/creating-shop-page.md
- Shop Page Template: frontend/03-implementation-guides/shop-section/shop-page-template.md
- Examples: frontend/03-implementation-guides/shop-section/shop-examples.md
- UI Components:
- Component Library: frontend/04-ui-components/component-library.md
- Forms: frontend/04-ui-components/forms.md
- Buttons: frontend/04-ui-components/buttons.md
- Cards: frontend/04-ui-components/cards.md
- Tables: frontend/04-ui-components/tables.md
- Modals: frontend/04-ui-components/modals.md
- Badges: frontend/04-ui-components/badges.md
- Alerts & Toasts: frontend/04-ui-components/alerts-toasts.md
- Icons: frontend/04-ui-components/icons.md
- Common Patterns:
- Data Loading: frontend/05-common-patterns/data-loading.md
- Pagination: frontend/05-common-patterns/pagination.md
- Filtering & Sorting: frontend/05-common-patterns/filtering-sorting.md
- Form Validation: frontend/05-common-patterns/form-validation.md
- CRUD Operations: frontend/05-common-patterns/crud-operations.md
- Real-time Updates: frontend/05-common-patterns/real-time-updates.md
- Advanced Topics:
- Performance: frontend/06-advanced-topics/performance.md
- Dark Mode: frontend/06-advanced-topics/dark-mode.md
- Accessibility: frontend/06-advanced-topics/accessibility.md
- Responsive Design: frontend/06-advanced-topics/responsive-design.md
- Debugging: frontend/06-advanced-topics/debugging.md
- Testing:
- Testing Overview: frontend/07-testing/testing-overview.md
- Testing Hub Guide: frontend/07-testing/testing-hub-guide.md
- Manual Testing: frontend/07-testing/manual-testing-checklist.md
- Reference:
- Alpine.js Quick Reference: frontend/08-reference/alpine-js-reference.md
- API Client Reference: frontend/08-reference/api-client-reference.md
- Utils Reference: frontend/08-reference/utils-reference.md
- CSS Classes: frontend/08-reference/css-classes.md
- Troubleshooting: frontend/08-reference/troubleshooting.md
# Existing sections
- API:
- Overview: api/index.md
# ... rest of API docs
- Testing:
- Testing Guide: testing/testing-guide.md
- Test Maintenance: testing/test-maintenance.md
# Backend docs come later
# - Backend:
# - Architecture: backend/architecture.md
# - ...
```
---
## 🎯 Success Metrics
### Team Adoption
- [ ] 100% of team can create a basic admin page in <15 minutes
- [ ] 90% of new pages follow architecture correctly on first try
- [ ] <5% of PRs have sidebar indicator issues
- [ ] <10% of PRs have variable naming conflicts
### Documentation Quality
- [ ] Every guide has working code examples
- [ ] Every guide has a testing checklist
- [ ] Every guide links to related topics
- [ ] Every guide has time estimates
### Onboarding Speed
- [ ] New developers can create first page within 1 day
- [ ] New developers can work independently within 3 days
- [ ] Reduced onboarding questions by 80%
---
## 📅 Implementation Timeline
### Week 1: Core Essentials
- Write Alpine.js Architecture guide
- Write Creating Admin Page guide
- Create Admin Page Template
- Write Common Pitfalls guide
### Week 2: Components & Patterns
- Document Component Library
- Document Forms, Buttons, Cards
- Document Pagination pattern
- Document CRUD operations
### Week 3: Reference & Vendor
- Complete Reference section
- Write Vendor section guides
- Create Vendor templates
### Week 4: Shop & Polish
- Write Shop section guides
- Create Shop templates
- Review and polish all docs
- Add missing cross-references
### Week 5: Testing & Launch
- Internal review with team
- Fix any issues found
- Launch documentation
- Gather feedback
---
## 🔄 Maintenance Plan
### Regular Updates
- **Weekly:** Check for new common issues
- **Monthly:** Review and update examples
- **Quarterly:** Major documentation review
### Version Control
- All documentation in Git
- Changes reviewed like code
- Version numbers for major updates
### Feedback Loop
- Add "Was this helpful?" to each page
- Collect common questions
- Update docs based on feedback
---
## 📊 Documentation Metrics
Track these metrics:
1. **Page views** - Which docs are most used?
2. **Search terms** - What are people looking for?
3. **Time on page** - Are docs too long/short?
4. **Bounce rate** - Are people finding what they need?
5. **Questions in Slack** - Are docs answering questions?
---
## 🎓 Learning Path for New Developers
### Day 1: Foundations
1. Read: Overview
2. Read: Alpine.js Architecture
3. Read: Creating Admin Page guide
4. Exercise: Create a simple "Hello World" admin page
### Day 2: Practice
1. Read: Component Library
2. Read: Data Loading pattern
3. Exercise: Create admin page that loads data from API
### Day 3: Patterns
1. Read: Pagination pattern
2. Read: CRUD operations
3. Exercise: Create full CRUD page with pagination
### Day 4: Real Work
1. Read: Common Pitfalls
2. Read: Troubleshooting
3. Exercise: Implement first real feature
### Day 5: Independence
- Work on real tickets independently
- Refer to docs as needed
- Ask questions when stuck
---
## 📝 Documentation Templates
### Guide Template
```markdown
# [Feature/Pattern Name]
## Overview
Brief 2-3 sentence description
## Prerequisites
- [ ] Requirement 1
- [ ] Requirement 2
## Quick Start (5 minutes)
Fastest path to working code
## Step-by-Step Guide
Detailed instructions
## Common Issues
Problems and solutions
## Testing Checklist
- [ ] Test 1
- [ ] Test 2
## See Also
- [Related doc 1](link)
- [Related doc 2](link)
```
### Reference Template
```markdown
# [API/Component Name] Reference
## Overview
What it does
## Usage
Basic usage example
## API
Full API documentation
## Examples
Multiple real-world examples
## See Also
Related references
```
---
## ✅ Ready to Implement
This documentation plan provides:
1. **Clear structure** - Organized by role and task
2. **Practical focus** - Implementation guides, not theory
3. **Real examples** - Based on actual working code
4. **Team-oriented** - Designed for collaboration
5. **Maintainable** - Easy to update and extend
**Next Steps:**
1. Review and approve this plan
2. Start with Phase 1 (Core Essentials)
3. Write docs one at a time
4. Get team feedback early
5. Iterate and improve
**Estimated effort:**
- 5 weeks for initial documentation
- 2-4 hours per week for maintenance
- Massive time savings for team (100+ hours/year)
---
## 🎉 Benefits
Once complete, your team will have:
**Faster development** - 10-15 minute page creation
**Fewer bugs** - Common mistakes documented
**Better code quality** - Patterns enforced through docs
**Easier onboarding** - New devs productive in days
**Reduced questions** - Self-service documentation
**Scalable knowledge** - Team expertise captured
**ROI:** Pays for itself after 2-3 features implemented! 📈

View File

@@ -1,399 +0,0 @@
# Work Plan - October 22, 2025
## Jinja2 Migration: Polish & Complete Admin Panel
**Current Status:** Core migration complete ✅ | Auth loop fixed ✅ | Minor issues remaining ⚠️
---
## 🎯 Today's Goals
1. ✅ Fix icon system and utils.js conflicts
2. ✅ Test and verify logout flow
3. ✅ Test all admin pages (vendors, users)
4. ✅ Create remaining templates
5. ✅ Clean up and remove old code
**Estimated Time:** 3-4 hours
---
## 📋 Task List
### Priority 1: Fix Icon/Utils Conflicts (HIGH) ⚠️
**Issue Reported:**
> "Share some outputs about $icons issues and utils already declared"
#### Task 1.1: Investigate Icon Issues
- [ ] Check browser console for icon-related errors
- [ ] Verify `icons.js` is loaded only once
- [ ] Check for duplicate `window.icon` declarations
- [ ] Test icon rendering in all templates
**Files to Check:**
- `static/shared/js/icons.js`
- `app/templates/admin/base.html` (script order)
- `app/templates/admin/login.html` (script order)
**Expected Issues:**
```javascript
// Possible duplicate declaration
Uncaught SyntaxError: Identifier 'icon' has already been declared
// or
Warning: window.icon is already defined
```
**Fix:**
- Ensure `icons.js` loaded only once per page
- Remove any duplicate `icon()` function declarations
- Verify Alpine magic helper `$icon()` is registered correctly
#### Task 1.2: Investigate Utils Issues
- [ ] Check for duplicate `Utils` object declarations
- [ ] Verify `utils.js` loaded only once
- [ ] Test all utility functions (formatDate, showToast, etc.)
**Files to Check:**
- `static/shared/js/utils.js`
- `static/shared/js/api-client.js` (Utils defined here too?)
**Potential Fix:**
```javascript
// Option 1: Use namespace to avoid conflicts
if (typeof window.Utils === 'undefined') {
window.Utils = { /* ... */ };
}
// Option 2: Remove duplicate definitions
// Keep Utils only in one place (either utils.js OR api-client.js)
```
---
### Priority 2: Test Logout Flow (HIGH) 🔐
#### Task 2.1: Test Logout Button
- [ ] Click logout in header
- [ ] Verify cookie is deleted
- [ ] Verify localStorage is cleared
- [ ] Verify redirect to login page
- [ ] Verify cannot access dashboard after logout
**Test Script:**
```javascript
// Before logout
console.log('Cookie:', document.cookie);
console.log('localStorage:', localStorage.getItem('admin_token'));
// Click logout
// After logout (should be empty)
console.log('Cookie:', document.cookie); // Should not contain admin_token
console.log('localStorage:', localStorage.getItem('admin_token')); // Should be null
```
#### Task 2.2: Update Logout Endpoint (if needed)
**File:** `app/api/v1/admin/auth.py`
Already implemented, just verify:
```python
@router.post("/logout")
def admin_logout(response: Response):
# Clears the cookie
response.delete_cookie(key="admin_token", path="/")
return {"message": "Logged out successfully"}
```
#### Task 2.3: Update Header Logout Button
**File:** `app/templates/partials/header.html`
Verify logout button calls the correct endpoint:
```html
<button @click="handleLogout()">
Logout
</button>
<script>
function handleLogout() {
// Call logout API
fetch('/api/v1/admin/auth/logout', { method: 'POST' })
.then(() => {
// Clear localStorage
localStorage.clear();
// Redirect
window.location.href = '/admin/login';
});
}
</script>
```
---
### Priority 3: Test All Admin Pages (MEDIUM) 📄
#### Task 3.1: Test Vendors Page
- [ ] Navigate to `/admin/vendors`
- [ ] Verify page loads with authentication
- [ ] Check if template exists or needs creation
- [ ] Test vendor list display
- [ ] Test vendor creation button
**If template missing:**
Create `app/templates/admin/vendors.html`
#### Task 3.2: Test Users Page
- [ ] Navigate to `/admin/users`
- [ ] Verify page loads with authentication
- [ ] Check if template exists or needs creation
- [ ] Test user list display
**If template missing:**
Create `app/templates/admin/users.html`
#### Task 3.3: Test Navigation
- [ ] Click all sidebar links
- [ ] Verify no 404 errors
- [ ] Verify active state highlights correctly
- [ ] Test breadcrumbs (if applicable)
---
### Priority 4: Create Missing Templates (MEDIUM) 📝
#### Task 4.1: Create Vendors Template
**File:** `app/templates/admin/vendors.html`
```jinja2
{% extends "admin/base.html" %}
{% block title %}Vendors Management{% endblock %}
{% block alpine_data %}adminVendors(){% endblock %}
{% block content %}
<div class="my-6">
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Vendors Management
</h2>
</div>
<!-- Vendor list content -->
<div x-data="adminVendors()">
<!-- Your existing vendors.html content here -->
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/vendors.js') }}"></script>
{% endblock %}
```
#### Task 4.2: Create Users Template
**File:** `app/templates/admin/users.html`
Similar structure to vendors template.
#### Task 4.3: Verify Vendor Edit Page
Check if vendor-edit needs a template or if it's a modal/overlay.
---
### Priority 5: Cleanup (LOW) 🧹
#### Task 5.1: Remove Old Static HTML Files
- [ ] Delete `static/admin/dashboard.html` (if exists)
- [ ] Delete `static/admin/vendors.html` (if exists)
- [ ] Delete `static/admin/users.html` (if exists)
- [ ] Delete `static/admin/partials/` directory
**Before deleting:** Backup files just in case!
#### Task 5.2: Remove Partial Loader
- [ ] Delete `static/shared/js/partial-loader.js`
- [ ] Remove any references to `partialLoader` in code
- [ ] Search codebase: `grep -r "partial-loader" .`
#### Task 5.3: Clean Up frontend.py
**File:** `app/routes/frontend.py`
- [ ] Remove commented-out admin routes
- [ ] Or delete file entirely if only contained admin routes
- [ ] Update imports if needed
#### Task 5.4: Production Mode Preparation
- [ ] Set log levels to production (INFO or WARN)
- [ ] Update cookie `secure=True` for production
- [ ] Remove debug console.logs
- [ ] Test with production settings
**Update log levels:**
```javascript
// static/admin/js/log-config.js
GLOBAL_LEVEL: isDevelopment ? 4 : 2, // Debug in dev, Warnings in prod
LOGIN: isDevelopment ? 4 : 1, // Full debug in dev, errors only in prod
API_CLIENT: isDevelopment ? 3 : 1, // Info in dev, errors only in prod
```
---
## 🧪 Testing Checklist
### Comprehensive Testing
- [ ] Fresh login (clear all data first)
- [ ] Dashboard loads correctly
- [ ] Stats cards display data
- [ ] Recent vendors table works
- [ ] Sidebar navigation works
- [ ] Dark mode toggle works
- [ ] Logout clears auth and redirects
- [ ] Cannot access dashboard after logout
- [ ] Vendors page loads
- [ ] Users page loads
- [ ] No console errors
- [ ] No 404 errors in Network tab
- [ ] Icons display correctly
- [ ] All Alpine.js components work
### Browser Testing
- [ ] Chrome/Edge
- [ ] Firefox
- [ ] Safari (if available)
- [ ] Mobile view (responsive)
---
## 🐛 Debugging Guide
### If Icons Don't Display:
```javascript
// Check in console:
console.log('window.icon:', typeof window.icon);
console.log('window.Icons:', typeof window.Icons);
console.log('$icon available:', typeof Alpine !== 'undefined' && Alpine.magic('icon'));
// Test manually:
document.body.innerHTML += window.icon('home', 'w-6 h-6');
```
### If Utils Undefined:
```javascript
// Check in console:
console.log('Utils:', typeof Utils);
console.log('Utils methods:', Object.keys(Utils || {}));
// Test manually:
Utils.showToast('Test message', 'info');
```
### If Auth Fails:
```javascript
// Check storage:
console.log('localStorage token:', localStorage.getItem('admin_token'));
console.log('Cookie:', document.cookie);
// Test API manually:
fetch('/api/v1/admin/auth/me', {
headers: { 'Authorization': `Bearer ${localStorage.getItem('admin_token')}` }
}).then(r => r.json()).then(console.log);
```
---
## 📝 Documentation Tasks
### Update Documentation
- [ ] Update project README with new architecture
- [ ] Document authentication flow (cookies + localStorage)
- [ ] Document template structure
- [ ] Add deployment notes (dev vs production)
- [ ] Update API documentation if needed
### Code Comments
- [ ] Add comments to complex authentication code
- [ ] Document cookie settings and rationale
- [ ] Explain dual token storage pattern
- [ ] Add JSDoc comments to JavaScript functions
---
## 🚀 Next Phase Preview (After Today)
### Vendor Portal Migration
1. Apply same Jinja2 pattern to vendor routes
2. Create vendor templates (login, dashboard, etc.)
3. Implement vendor authentication (separate cookie: `vendor_token`)
4. Test vendor flows
### Customer/Shop Migration
1. Customer authentication system
2. Shop templates
3. Shopping cart (consider cookie vs localStorage)
4. "Remember Me" implementation
### Advanced Features
1. "Remember Me" checkbox (30-day cookies)
2. Session management
3. Multiple device logout
4. Security enhancements (CSRF tokens)
---
## ⏰ Time Estimates
| Task | Estimated Time | Priority |
|------|---------------|----------|
| Fix icon/utils issues | 30-45 min | HIGH |
| Test logout flow | 15-30 min | HIGH |
| Test admin pages | 30 min | MEDIUM |
| Create missing templates | 45-60 min | MEDIUM |
| Cleanup old code | 30 min | LOW |
| Testing & verification | 30-45 min | HIGH |
| Documentation | 30 min | LOW |
**Total: 3-4 hours**
---
## ✅ Success Criteria for Today
By end of day, we should have:
- [ ] All icons displaying correctly
- [ ] No JavaScript errors in console
- [ ] Logout flow working perfectly
- [ ] All admin pages accessible and working
- [ ] Templates for vendors and users pages
- [ ] Old code cleaned up
- [ ] Comprehensive testing completed
- [ ] Documentation updated
---
## 🎯 Stretch Goals (If Time Permits)
1. Add loading states to all buttons
2. Improve error messages (user-friendly)
3. Add success/error toasts to all operations
4. Implement "Remember Me" checkbox
5. Start vendor portal migration
6. Add unit tests for authentication
---
## 📞 Support Resources
### If Stuck:
- Review yesterday's complete file implementations
- Check browser console for detailed logs (log level 4)
- Use test-auth-flow.html for systematic testing
- Check Network tab for HTTP requests/responses
### Reference Files:
- `static/admin/test-auth-flow.html` - Testing interface
- `TESTING_CHECKLIST.md` - Systematic testing guide
- Yesterday's complete file updates (in conversation)
---
**Good luck with today's tasks! 🚀**
Remember: Take breaks, test

View File

@@ -1,520 +0,0 @@
# Jinja2 Migration Progress - Admin Panel
**Date:** October 20, 2025
**Project:** Multi-Tenant E-commerce Platform
**Goal:** Migrate from static HTML files to Jinja2 server-rendered templates
---
## 🎯 Current Status: DEBUGGING AUTH LOOP
We successfully set up the Jinja2 infrastructure but are experiencing authentication redirect loops. We're in the process of simplifying the auth flow to resolve this.
---
## ✅ What's Been Completed
### 1. Infrastructure Setup ✅
- [x] Added Jinja2Templates to `main.py`
- [x] Created `app/templates/` directory structure
- [x] Created `app/api/v1/admin/pages.py` for HTML routes
- [x] Integrated pages router into the main app
**Files Created:**
```
app/
├── templates/
│ ├── admin/
│ │ ├── base.html ✅ Created
│ │ ├── login.html ✅ Created
│ │ └── dashboard.html ✅ Created
│ └── partials/
│ ├── header.html ✅ Moved from static
│ └── sidebar.html ✅ Moved from static
└── api/
└── v1/
└── admin/
└── pages.py ✅ Created
```
### 2. Route Configuration ✅
**New Jinja2 Routes (working):**
- `/admin/` → redirects to `/admin/dashboard`
- `/admin/login` → login page (no auth)
- `/admin/dashboard` → dashboard page (requires auth)
- `/admin/vendors` → vendors page (requires auth)
- `/admin/users` → users page (requires auth)
**Old Static Routes (disabled):**
- Commented out admin routes in `app/routes/frontend.py`
- Old `/static/admin/*.html` routes no longer active
### 3. Exception Handler Updates ✅
- [x] Updated `app/exceptions/handler.py` to redirect HTML requests on 401
- [x] Added `_is_html_page_request()` helper function
- [x] Server-side redirects working for unauthenticated page access
### 4. JavaScript Updates ✅
Updated all JavaScript files to use new routes:
**Files Updated:**
- `static/admin/js/dashboard.js` - viewVendor() uses `/admin/vendors`
- `static/admin/js/login.js` - redirects to `/admin/dashboard`
- `static/admin/js/vendors.js` - auth checks use `/admin/login`
- `static/admin/js/vendor-edit.js` - all redirects updated
- `static/shared/js/api-client.js` - handleUnauthorized() uses `/admin/login`
### 5. Template Structure ✅
**Base Template (`app/templates/admin/base.html`):**
- Server-side includes for header and sidebar (no more AJAX loading!)
- Proper script loading order
- Alpine.js integration
- No more `partial-loader.js`
**Dashboard Template (`app/templates/admin/dashboard.html`):**
- Extends base template
- Uses Alpine.js `adminDashboard()` component
- Stats cards and recent vendors table
**Login Template (`app/templates/admin/login.html`):**
- Standalone page (doesn't extend base)
- Uses Alpine.js `adminLogin()` component
---
## ❌ Current Problem: Authentication Loop
### Issue Description
Getting infinite redirect loops in various scenarios:
1. After login → redirects back to login
2. On login page → continuous API calls to `/admin/auth/me`
3. Dashboard → redirects to login → redirects to dashboard
### Root Causes Identified
1. **Multiple redirect handlers fighting:**
- Server-side: `handler.py` redirects on 401 for HTML pages
- Client-side: `api-client.js` also redirects on 401
- Both triggering simultaneously
2. **Login page checking auth on init:**
- Calls `/admin/auth/me` on page load
- Gets 401 → triggers redirect
- Creates loop
3. **Token not being sent properly:**
- Token stored but API calls not including it
- Gets 401 even with valid token
### Latest Approach (In Progress)
Simplifying to minimal working version:
- Login page does NOTHING on init (no auth checking)
- API client does NOT redirect (just throws errors)
- Server ONLY redirects browser HTML requests (not API calls)
- One source of truth for auth handling
---
## 📝 Files Modified (Complete List)
### Backend Files
1. **`main.py`**
```python
# Added:
- Jinja2Templates import and configuration
- admin_pages router include at /admin prefix
```
2. **`app/api/main.py`** (unchanged - just includes v1 routes)
3. **`app/api/v1/admin/__init__.py`**
```python
# Added:
- import pages
- router.include_router(pages.router, tags=["admin-pages"])
```
4. **`app/api/v1/admin/pages.py`** (NEW FILE)
```python
# Contains:
- @router.get("/") - root redirect
- @router.get("/login") - login page
- @router.get("/dashboard") - dashboard page
- @router.get("/vendors") - vendors page
- @router.get("/users") - users page
```
5. **`app/routes/frontend.py`**
```python
# Changed:
- Commented out all /admin/ routes
- Left vendor and shop routes active
```
6. **`app/exceptions/handler.py`**
```python
# Added:
- 401 redirect logic for HTML pages
- _is_html_page_request() helper
# Status: Needs simplification
```
### Frontend Files
1. **`static/admin/js/login.js`**
```javascript
// Changed:
- Removed /static/admin/ paths
- Updated to /admin/ paths
- checkExistingAuth() logic
# Status: Needs simplification
```
2. **`static/admin/js/dashboard.js`**
```javascript
// Changed:
- viewVendor() uses /admin/vendors
# Status: Working
```
3. **`static/admin/js/vendors.js`**
```javascript
// Changed:
- checkAuth() redirects to /admin/login
- handleLogout() redirects to /admin/login
# Status: Not tested yet
```
4. **`static/admin/js/vendor-edit.js`**
```javascript
// Changed:
- All /static/admin/ paths to /admin/
# Status: Not tested yet
```
5. **`static/shared/js/api-client.js`**
```javascript
// Changed:
- handleUnauthorized() uses /admin/login
# Status: Needs simplification - causing loops
```
6. **`static/shared/js/utils.js`** (unchanged - working fine)
### Template Files (NEW)
1. **`app/templates/admin/base.html`** ✅
- Master layout with sidebar and header
- Script loading in correct order
- No partial-loader.js
2. **`app/templates/admin/login.html`** ✅
- Standalone login page
- Alpine.js adminLogin() component
3. **`app/templates/admin/dashboard.html`** ✅
- Extends base.html
- Alpine.js adminDashboard() component
4. **`app/templates/admin/partials/header.html`** ✅
- Top navigation bar
- Updated logout link to /admin/login
5. **`app/templates/admin/partials/sidebar.html`** ✅
- Side navigation menu
- Updated all links to /admin/* paths
---
## 🔧 Next Steps (Tomorrow)
### Immediate Priority: Fix Auth Loop
Apply the simplified approach from the last message:
1. **Simplify `login.js`:**
```javascript
// Remove all auth checking on init
// Just show login form
// Only redirect after successful login
```
2. **Simplify `api-client.js`:**
```javascript
// Remove handleUnauthorized() redirect logic
// Just throw errors, don't redirect
// Let server handle redirects
```
3. **Simplify `handler.py`:**
```javascript
// Only redirect browser HTML requests (text/html accept header)
// Don't redirect API calls (application/json)
// Don't redirect if already on login page
```
**Test Flow:**
1. Navigate to `/admin/login` → should show form (no loops)
2. Login → should redirect to `/admin/dashboard`
3. Dashboard → should load with sidebar/header
4. No console errors, no 404s for partials
### After Auth Works
1. **Create remaining page templates:**
- `app/templates/admin/vendors.html`
- `app/templates/admin/users.html`
- `app/templates/admin/vendor-edit.html`
2. **Test all admin flows:**
- Login ✓
- Dashboard ✓
- Vendors list
- Vendor create
- Vendor edit
- User management
3. **Cleanup:**
- Remove old static HTML files
- Remove `app/routes/frontend.py` admin routes completely
- Remove `partial-loader.js`
4. **Migrate vendor portal:**
- Same process for `/vendor/*` routes
- Create vendor templates
- Update vendor JavaScript files
---
## 📚 Key Learnings
### What Worked
1. ✅ **Server-side template rendering** - Clean, fast, no AJAX for partials
2. ✅ **Jinja2 integration** - Easy to set up, works with FastAPI
3. ✅ **Route separation** - HTML routes in `pages.py`, API routes separate
4. ✅ **Template inheritance** - `base.html` + `{% extends %}` pattern
### What Caused Issues
1. ❌ **Multiple redirect handlers** - Client + server both handling 401
2. ❌ **Auth checking on login page** - Created loops
3. ❌ **Complex error handling** - Too many places making decisions
4. ❌ **Path inconsistencies** - Old `/static/admin/` vs new `/admin/`
### Best Practices Identified
1. **Single source of truth for redirects** - Choose server OR client, not both
2. **Login page should be dumb** - No auth checking, just show form
3. **API client should be simple** - Fetch data, throw errors, don't redirect
4. **Server handles page-level auth** - FastAPI dependencies + exception handler
5. **Clear separation** - HTML pages vs API endpoints
---
## 🗂️ Project Structure (Current)
```
project/
├── main.py ✅ Updated
├── app/
│ ├── api/
│ │ ├── main.py ✅ Unchanged
│ │ └── v1/
│ │ └── admin/
│ │ ├── __init__.py ✅ Updated
│ │ ├── pages.py ✅ NEW
│ │ ├── auth.py ✅ Existing (API routes)
│ │ ├── vendors.py ✅ Existing (API routes)
│ │ └── dashboard.py ✅ Existing (API routes)
│ ├── routes/
│ │ └── frontend.py ⚠️ Partially disabled
│ ├── exceptions/
│ │ └── handler.py ⚠️ Needs simplification
│ └── templates/ ✅ NEW
│ ├── admin/
│ │ ├── base.html
│ │ ├── login.html
│ │ └── dashboard.html
│ └── partials/
│ ├── header.html
│ └── sidebar.html
└── static/
├── admin/
│ ├── js/
│ │ ├── login.js ⚠️ Needs simplification
│ │ ├── dashboard.js ✅ Updated
│ │ ├── vendors.js ✅ Updated
│ │ └── vendor-edit.js ✅ Updated
│ └── css/
│ └── tailwind.output.css ✅ Unchanged
└── shared/
└── js/
├── api-client.js ⚠️ Needs simplification
├── utils.js ✅ Working
└── icons.js ✅ Working
```
**Legend:**
- ✅ = Working correctly
- ⚠️ = Needs attention/debugging
- ❌ = Not working/causing issues
---
## 🐛 Debug Commands
### Clear localStorage (Browser Console)
```javascript
localStorage.clear();
```
### Check stored tokens
```javascript
console.log('admin_token:', localStorage.getItem('admin_token'));
console.log('admin_user:', localStorage.getItem('admin_user'));
```
### Test API call manually
```javascript
fetch('/api/v1/admin/auth/me', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('admin_token')}`
}
}).then(r => r.json()).then(d => console.log(d));
```
### Check current route
```javascript
console.log('Current path:', window.location.pathname);
console.log('Full URL:', window.location.href);
```
---
## 📖 Reference: Working Code Snippets
### Minimal Login.js (To Try Tomorrow)
```javascript
function adminLogin() {
return {
dark: false,
credentials: { username: '', password: '' },
loading: false,
error: null,
success: null,
errors: {},
init() {
this.dark = localStorage.getItem('theme') === 'dark';
// NO AUTH CHECKING - just show form
},
async handleLogin() {
if (!this.validateForm()) return;
this.loading = true;
try {
const response = await fetch('/api/v1/admin/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: this.credentials.username,
password: this.credentials.password
})
});
const data = await response.json();
if (!response.ok) throw new Error(data.message);
localStorage.setItem('admin_token', data.access_token);
localStorage.setItem('admin_user', JSON.stringify(data.user));
this.success = 'Login successful!';
setTimeout(() => window.location.href = '/admin/dashboard', 500);
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
}
}
```
### Simplified API Client Request Method
```javascript
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
...options,
headers: this.getHeaders(options.headers)
};
const response = await fetch(url, config);
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Request failed');
}
return data;
// NO REDIRECT LOGIC HERE!
}
```
### Simplified Exception Handler
```python
if exc.status_code == 401:
accept_header = request.headers.get("accept", "")
is_browser = "text/html" in accept_header
if is_browser and not request.url.path.endswith("/login"):
if request.url.path.startswith("/admin"):
return RedirectResponse(url="/admin/login", status_code=302)
# Return JSON for API calls
return JSONResponse(status_code=exc.status_code, content=exc.to_dict())
```
---
## 💡 Questions to Answer Tomorrow
1. Does the simplified auth flow work without loops?
2. Can we successfully login and access dashboard?
3. Are tokens being sent correctly in API requests?
4. Do we need the auth check on login page at all?
5. Should we move ALL redirect logic to server-side?
---
## 🎯 Success Criteria
The migration will be considered successful when:
- [ ] Login page loads without loops
- [ ] Login succeeds and redirects to dashboard
- [ ] Dashboard displays with sidebar and header
- [ ] No 404 errors for partials
- [ ] Icons display correctly
- [ ] Stats cards load data from API
- [ ] Navigation between admin pages works
- [ ] Logout works correctly
---
**End of Session - October 20, 2025**
Good work today! We made significant progress on the infrastructure. Tomorrow we'll resolve the auth loop and complete the admin panel migration.

View File

@@ -1,91 +0,0 @@
# Product Independence - Quick Reference
## TL;DR
**Problem:** Products must have a MarketplaceProduct entry, even for vendor-created products.
**Solution:** Make `marketplace_product_id` nullable and add core product fields to Product table.
**Impact:** 6-8 weeks implementation, requires database migration.
---
## Current Blocker for Seed Script
The seed script fails because it tries to create standalone products, but the current schema requires:
```python
marketplace_product_id = Column(..., nullable=False) # ❌ MANDATORY
```
### Temporary Workaround (Current Seed Script)
Create MarketplaceProduct for every Product until migration is complete:
```python
# For each product:
# 1. Create MarketplaceProduct
# 2. Create Product linked to it
```
This works but violates the desired architecture.
---
## Quick Decision Matrix
| Question | Answer | Priority |
|----------|--------|----------|
| Implement now? | Not urgent - current workaround functional | Medium |
| Block for launch? | No - can ship with current architecture | N/A |
| Technical debt? | Yes - should address in 1-2 quarters | Medium-High |
---
## Minimal Implementation (if needed quickly)
**Phase 1 Only - Make nullable:**
```python
# 1 hour migration
def upgrade():
op.alter_column('products', 'marketplace_product_id', nullable=True)
op.add_column('products', sa.Column('title', sa.String(), nullable=True))
op.add_column('products', sa.Column('gtin', sa.String(), nullable=True))
# Add only critical fields
# Updated model
class Product:
marketplace_product_id = Column(Integer, ForeignKey(...), nullable=True) # ✅
title = Column(String, nullable=True) # Temp nullable
gtin = Column(String, nullable=True)
```
Then gradually add remaining fields in future migrations.
---
## Key Stakeholders to Consult
- [ ] Product Manager - Business impact, priority
- [ ] Lead Developer - Technical approach, timeline
- [ ] DevOps - Migration strategy, rollback plan
- [ ] Vendors (if beta testing) - Feature importance
---
## Next Steps
1. **Review full migration plan:** `/outputs/PRODUCT_MIGRATION_PLAN.md`
2. **Discuss with team** - Get buy-in on approach
3. **Schedule implementation** - Based on priority
4. **Create tracking ticket** - Link to migration plan
---
## For Now: Use Workaround
The updated `seed_demo.py` creates both MarketplaceProduct and Product.
This is temporary until migration is implemented.
**No immediate action required** - continue development with current architecture.

View File

@@ -1,284 +0,0 @@
╔══════════════════════════════════════════════════════════════════╗
║ ROUTE MIGRATION: Static → Jinja2 Templates ║
╚══════════════════════════════════════════════════════════════════╝
📦 WHAT YOU GET
═════════════════════════════════════════════════════════════════
3 New Route Files:
├─ admin_pages.py ......... Admin HTML routes (12 routes)
├─ vendor_pages.py ........ Vendor HTML routes (13 routes)
└─ shop_pages.py .......... Shop HTML routes (19 routes)
1 Migration Guide:
└─ MIGRATION_GUIDE.md ..... Complete step-by-step guide
🎯 KEY IMPROVEMENTS
═════════════════════════════════════════════════════════════════
Before (frontend.py):
❌ Static FileResponse
❌ No authentication
❌ No dynamic content
❌ Poor SEO
After (New files):
✅ Jinja2 Templates
✅ Server-side auth
✅ Dynamic rendering
✅ Better SEO
📁 FILE STRUCTURE
═════════════════════════════════════════════════════════════════
app/
├── api/v1/
│ ├── admin/
│ │ └── pages.py ←─────────── Admin routes (NEW)
│ ├── vendor/
│ │ └── pages.py ←─────────── Vendor routes (NEW)
│ └── shop/
│ └── pages.py ←─────────── Shop routes (NEW)
├── routes/
│ └── frontend.py ←──────────── DELETE after migration
└── templates/
├── admin/ ←────────────── Admin HTML templates
├── vendor/ ←────────────── Vendor HTML templates
└── shop/ ←────────────── Shop HTML templates
🚀 QUICK INSTALL (5 STEPS)
═════════════════════════════════════════════════════════════════
Step 1: Create directories
$ mkdir -p app/api/v1/admin
$ mkdir -p app/api/v1/vendor
$ mkdir -p app/api/v1/shop
Step 2: Copy new route files
$ cp admin_pages.py app/api/v1/admin/pages.py
$ cp vendor_pages.py app/api/v1/vendor/pages.py
$ cp shop_pages.py app/api/v1/shop/pages.py
Step 3: Update main router (see MIGRATION_GUIDE.md)
- Include new routers in app/api/v1/router.py
Step 4: Test routes
$ uvicorn app.main:app --reload
- Visit http://localhost:8000/admin/dashboard
- Visit http://localhost:8000/vendor/ACME/dashboard
- Visit http://localhost:8000/shop/
Step 5: Remove old frontend.py
$ mv app/routes/frontend.py app/routes/frontend.py.backup
📋 ROUTE BREAKDOWN
═════════════════════════════════════════════════════════════════
Admin Routes (admin_pages.py):
✅ /admin/ → Redirect to login
✅ /admin/login → Login page
✅ /admin/dashboard → Dashboard
✅ /admin/vendors → Vendor list
✅ /admin/vendors/create → Create vendor form
✅ /admin/vendors/{code} → Vendor details
✅ /admin/vendors/{code}/edit → Edit vendor form
✅ /admin/users → User management
✅ /admin/imports → Import history
✅ /admin/settings → Platform settings
Vendor Routes (vendor_pages.py):
✅ /vendor/{code}/ → Redirect to login
✅ /vendor/{code}/login → Login page
✅ /vendor/{code}/dashboard → Dashboard
✅ /vendor/{code}/products → Product management
✅ /vendor/{code}/orders → Order management
✅ /vendor/{code}/customers → Customer management
✅ /vendor/{code}/inventory → Inventory management
✅ /vendor/{code}/marketplace → Marketplace imports
✅ /vendor/{code}/team → Team management
✅ /vendor/{code}/settings → Vendor settings
✅ /vendor/login → Fallback login (query param)
✅ /vendor/dashboard → Fallback dashboard
Shop Routes (shop_pages.py):
Public Routes:
✅ /shop/ → Homepage
✅ /shop/products → Product catalog
✅ /shop/products/{id} → Product detail
✅ /shop/categories/{slug} → Category page
✅ /shop/cart → Shopping cart
✅ /shop/checkout → Checkout
✅ /shop/search → Search results
✅ /shop/account/register → Registration
✅ /shop/account/login → Customer login
Authenticated Routes:
✅ /shop/account/dashboard → Account dashboard
✅ /shop/account/orders → Order history
✅ /shop/account/orders/{id} → Order detail
✅ /shop/account/profile → Profile settings
✅ /shop/account/addresses → Address management
✅ /shop/account/wishlist → Wishlist
✅ /shop/account/settings → Account settings
Static Pages:
✅ /shop/about → About us
✅ /shop/contact → Contact us
✅ /shop/faq → FAQ
✅ /shop/privacy → Privacy policy
✅ /shop/terms → Terms & conditions
🔑 KEY FEATURES
═════════════════════════════════════════════════════════════════
✅ Server-Side Authentication
- Admin routes require admin role
- Vendor routes require vendor role
- Shop account routes require customer role
- Public routes accessible to all
✅ Dynamic Content Rendering
- User data passed to templates
- Server-side rendering for SEO
- Context variables for personalization
✅ Template Inheritance
- Base templates for consistent layout
- Block overrides for page-specific content
- Shared components
✅ Proper URL Structure
- RESTful URL patterns
- Vendor code in URL path
- Clear separation of concerns
⚠️ IMPORTANT NOTES
═════════════════════════════════════════════════════════════════
Route Order Matters!
❌ WRONG:
@router.get("/vendors/{code}")
@router.get("/vendors/create") ← Never matches!
✅ CORRECT:
@router.get("/vendors/create") ← Specific first
@router.get("/vendors/{code}") ← Parameterized last
Authentication Dependencies:
- get_current_admin_user → Admin pages
- get_current_vendor_user → Vendor pages
- get_current_customer_user → Shop account pages
Template Paths:
- Must match directory structure
- Use forward slashes: "admin/dashboard.html"
- Base path: "app/templates/"
🧪 TESTING CHECKLIST
═════════════════════════════════════════════════════════════════
Admin Routes:
□ /admin/ redirects to /admin/login
□ /admin/login shows login form
□ /admin/dashboard requires auth
□ /admin/vendors shows vendor list
□ /admin/vendors/{code}/edit loads correctly
Vendor Routes:
□ /vendor/ACME/ redirects to login
□ /vendor/ACME/login shows login form
□ /vendor/ACME/dashboard requires auth
□ All /admin/* subroutes work
Shop Routes:
□ /shop/ shows products
□ /shop/products/{id} shows product
□ /shop/cart works without auth
□ /shop/account/dashboard requires auth
□ /shop/account/orders shows orders
🔧 COMMON ISSUES
═════════════════════════════════════════════════════════════════
Problem: "Template not found"
→ Check templates directory path
→ Verify template file exists
→ Check forward slashes in path
Problem: "401 Unauthorized"
→ Check auth dependency is defined
→ Verify token is being sent
→ Check user role matches requirement
Problem: "Route conflict"
→ Reorder routes (specific before parameterized)
→ Check for duplicate routes
→ Review route registration order
Problem: "Module not found"
→ Check __init__.py exists in directories
→ Verify import paths
→ Restart server after adding files
📊 COMPARISON TABLE
═════════════════════════════════════════════════════════════════
Feature │ Old (frontend.py) │ New (pages.py)
─────────────────┼───────────────────┼────────────────
Authentication │ Client-side │ Server-side ✅
Dynamic Content │ None │ Full Python ✅
Template Reuse │ Copy-paste │ Inheritance ✅
SEO │ Poor │ Good ✅
Security │ Client only │ Server validates ✅
Maintainability │ Medium │ High ✅
Lines of Code │ ~200 │ ~600 (organized) ✅
💡 PRO TIPS
═════════════════════════════════════════════════════════════════
1. Test incrementally
- Migrate one section at a time
- Keep old routes until new ones work
2. Use template inheritance
- Create base.html for each section
- Override blocks in child templates
3. Pass user data in context
- Available in templates via {{ user.name }}
- No extra API calls needed
4. Handle both auth and non-auth
- Some routes public (login, register)
- Some routes require auth (dashboard)
5. Follow RESTful patterns
- /vendors/create not /create-vendor
- /vendors/{code}/edit not /edit-vendor/{code}
📖 NEXT STEPS
═════════════════════════════════════════════════════════════════
1. Read MIGRATION_GUIDE.md for detailed steps
2. Install new route files
3. Update main router
4. Test each section
5. Remove old frontend.py
6. Update documentation
══════════════════════════════════════════════════════════════════
Migration made easy! 🚀
Your app is now using modern Jinja2 templates!
══════════════════════════════════════════════════════════════════

View File

@@ -1,512 +0,0 @@
# Implementation Roadmap
## Multi-Tenant Ecommerce Platform - Complete Development Guide
**Last Updated**: October 11, 2025
**Project Status**: Slice 1 In Progress (~75% complete)
---
## 📚 Documentation Structure
Your complete vertical slice documentation is organized as follows:
```
docs/slices/
├── 00_slices_overview.md ← Start here for overview
├── 00_implementation_roadmap.md ← This file - your guide
├── 01_slice1_admin_vendor_foundation.md
├── 02_slice2_marketplace_import.md
├── 03_slice3_product_catalog.md
├── 04_slice4_customer_shopping.md
└── 05_slice5_order_processing.md
```
---
## 🎯 Quick Start Guide
### For Current Development (Slice 1)
1. ✅ Read `01_slice1_admin_vendor_foundation.md`
2. ✅ Review what's marked as complete vs. in-progress
3. ⏳ Focus on vendor login and dashboard pages
4. ⏳ Complete testing checklist
5. ⏳ Deploy to staging
### For Future Slices
1. Complete current slice 100%
2. Read next slice documentation
3. Set up backend (models, schemas, services, APIs)
4. Build frontend (Jinja2 templates + Alpine.js)
5. Test thoroughly
6. Move to next slice
---
## 📊 Current Status Overview
### Slice 1: Multi-Tenant Foundation (75% Complete)
#### ✅ Completed
- Backend database models (User, Vendor, Role, VendorUser)
- Authentication system (JWT, bcrypt)
- Admin service layer (vendor creation with owner)
- Admin API endpoints (CRUD, dashboard)
- Vendor context middleware (subdomain + path detection)
- Admin login page (HTML + Alpine.js)
- Admin dashboard (HTML + Alpine.js)
- Admin vendor creation page (HTML + Alpine.js)
#### ⏳ In Progress
- Vendor login page (frontend)
- Vendor dashboard page (frontend)
- Testing and debugging
- Deployment configuration
#### 📋 To Do
- Complete vendor login/dashboard
- Full testing (see Slice 1 checklist)
- Documentation updates
- Staging deployment
### Slices 2-5: Not Started
All future slices have complete documentation ready to implement.
---
## 🗓️ Development Timeline
### Week 1: Slice 1 - Foundation ⏳ CURRENT
**Days 1-3**: ✅ Backend complete
**Days 4-5**: ⏳ Frontend completion
**Deliverable**: Admin can create vendors, vendors can log in
### Week 2: Slice 2 - Marketplace Import
**Days 1-3**: Backend (CSV import, Celery tasks, MarketplaceProduct model)
**Days 4-5**: Frontend (import UI with Alpine.js, status tracking)
**Deliverable**: Vendors can import products from Letzshop CSV
### Week 3: Slice 3 - Product Catalog
**Days 1-3**: Backend (Product model, publishing, inventory)
**Days 4-5**: Frontend (product management, catalog UI)
**Deliverable**: Vendors can manage product catalog
### Week 4: Slice 4 - Customer Shopping
**Days 1-3**: Backend (Customer model, Cart, public APIs)
**Days 4-5**: Frontend (shop pages, cart with Alpine.js)
**Deliverable**: Customers can browse and shop
### Week 5: Slice 5 - Order Processing
**Days 1-3**: Backend (Order model, checkout, order management)
**Days 4-5**: Frontend (checkout flow, order history)
**Deliverable**: Complete order workflow, platform ready for production
---
## 🎨 Technology Stack
### Backend
- **Framework**: FastAPI (Python 3.11+)
- **Database**: PostgreSQL + SQLAlchemy ORM
- **Authentication**: JWT tokens + bcrypt
- **Background Jobs**: Celery + Redis/RabbitMQ
- **API Docs**: Auto-generated OpenAPI/Swagger
### Frontend
- **Templating**: Jinja2 (server-side rendering)
- **JavaScript**: Alpine.js v3.x (15KB, CDN-based)
- **CSS**: Custom CSS with CSS variables
- **AJAX**: Fetch API (vanilla JavaScript)
- **No Build Step**: Everything runs directly in browser
### Why This Stack?
-**Alpine.js**: Lightweight reactivity without build complexity
-**Jinja2**: Server-side rendering for SEO and performance
-**No Build Step**: Faster development, easier deployment
-**FastAPI**: Modern Python, async support, auto-docs
-**PostgreSQL**: Robust, reliable, feature-rich
---
## 📋 Implementation Checklist
Use this checklist to track your progress across all slices:
### Slice 1: Foundation
- [x] Backend models created
- [x] Authentication system working
- [x] Admin service layer complete
- [x] Admin API endpoints working
- [x] Vendor context middleware working
- [x] Admin login page created
- [x] Admin dashboard created
- [x] Admin vendor creation page created
- [ ] Vendor login page created
- [ ] Vendor dashboard page created
- [ ] All tests passing
- [ ] Deployed to staging
### Slice 2: Marketplace Import
- [ ] MarketplaceProduct model
- [ ] MarketplaceImportJob model
- [ ] CSV processing service
- [ ] Celery tasks configured
- [ ] Import API endpoints
- [ ] Import UI pages
- [ ] Status tracking with Alpine.js
- [ ] All tests passing
### Slice 3: Product Catalog
- [ ] Product model complete
- [ ] Inventory model complete
- [ ] Product service layer
- [ ] Publishing logic
- [ ] Product API endpoints
- [ ] Product management UI
- [ ] Catalog browsing
- [ ] All tests passing
### Slice 4: Customer Shopping
- [ ] Customer model
- [ ] Cart model
- [ ] Customer service layer
- [ ] Cart service layer
- [ ] Public product APIs
- [ ] Shop homepage
- [ ] Product detail pages
- [ ] Shopping cart UI
- [ ] Customer registration/login
- [ ] All tests passing
### Slice 5: Order Processing
- [ ] Order model
- [ ] OrderItem model
- [ ] Order service layer
- [ ] Checkout logic
- [ ] Order API endpoints
- [ ] Checkout UI (multi-step)
- [ ] Customer order history
- [ ] Vendor order management
- [ ] Email notifications
- [ ] Payment integration (Stripe)
- [ ] All tests passing
- [ ] Production ready
---
## 🎯 Each Slice Must Include
### Backend Checklist
- [ ] Database models defined
- [ ] Pydantic schemas created
- [ ] Service layer implemented
- [ ] API endpoints created
- [ ] Exception handling added
- [ ] Database migrations applied
- [ ] Unit tests written
- [ ] Integration tests written
### Frontend Checklist
- [ ] Jinja2 templates created
- [ ] Alpine.js components implemented
- [ ] CSS styling applied
- [ ] API integration working
- [ ] Loading states added
- [ ] Error handling added
- [ ] Mobile responsive
- [ ] Browser tested (Chrome, Firefox, Safari)
### Documentation Checklist
- [ ] Slice documentation updated
- [ ] API endpoints documented
- [ ] Frontend components documented
- [ ] Testing checklist completed
- [ ] Known issues documented
- [ ] Next steps identified
---
## 🔧 Development Workflow
### Starting a New Slice
1. **Read Documentation**
```bash
# Open the slice markdown file
code docs/slices/0X_sliceX_name.md
```
2. **Set Up Backend**
```bash
# Create database models
# Create Pydantic schema
# Implement service layer
# Create API endpoints
# Write tests
```
3. **Set Up Frontend**
```bash
# Create Jinja2 templates
# Add Alpine.js components
# Style with CSS
# Test in browser
```
4. **Test Thoroughly**
```bash
# Run backend tests
pytest tests/
# Manual testing
# Use testing checklist in slice docs
```
5. **Deploy & Demo**
```bash
# Deploy to staging
# Demo to stakeholders
# Gather feedback
```
### Daily Development Flow
**Morning**
- Review slice documentation
- Identify today's goals (backend or frontend)
- Check testing checklist
**During Development**
- Follow code patterns from slice docs
- Use Alpine.js examples provided
- Keep vendor isolation in mind
- Test incrementally
**End of Day**
- Update slice documentation with progress
- Mark completed items in checklist
- Note any blockers or issues
- Commit code with meaningful messages
---
## 🎨 Alpine.js Patterns
### Basic Component Pattern
```javascript
function myComponent() {
return {
// State
data: [],
loading: false,
error: null,
// Lifecycle
init() {
this.loadData();
},
// Methods
async loadData() {
this.loading = true;
try {
this.data = await apiClient.get('/api/endpoint');
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
}
}
```
### Template Usage
```html
<div x-data="myComponent()" x-init="init()">
<div x-show="loading">Loading...</div>
<div x-show="error" x-text="error"></div>
<div x-show="!loading && !error">
<template x-for="item in data" :key="item.id">
<div x-text="item.name"></div>
</template>
</div>
</div>
```
### Common Directives
- `x-data` - Component state
- `x-init` - Initialization
- `x-show` - Toggle visibility
- `x-if` - Conditional rendering
- `x-for` - Loop through arrays
- `x-model` - Two-way binding
- `@click` - Event handling
- `:class` - Dynamic classes
- `x-text` - Text content
- `x-html` - HTML content
---
## 📚 Key Resources
### Documentation Files
- `00_slices_overview.md` - Complete overview
- `01_slice1_admin_vendor_foundation.md` - Current work
- `../quick_start_guide.md` - Setup guide
- `../css_structure_guide.txt` - CSS organization
- `../css_quick_reference.txt` - CSS usage
- `../12.project_readme_final.md` - Complete README
### External Resources
- [Alpine.js Documentation](https://alpinejs.dev/)
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
- [Jinja2 Documentation](https://jinja.palletsprojects.com/)
- [SQLAlchemy Documentation](https://docs.sqlalchemy.org/)
---
## 🚨 Common Pitfalls to Avoid
### Backend
- ❌ Forgetting vendor isolation in queries
- ❌ Not validating vendor_id in API endpoints
- ❌ Skipping database indexes
- ❌ Not handling edge cases
- ❌ Missing error handling
### Frontend
- ❌ Not handling loading states
- ❌ Not displaying error messages
- ❌ Forgetting mobile responsiveness
- ❌ Not testing in multiple browsers
- ❌ Mixing vendor contexts
### General
- ❌ Skipping tests
- ❌ Not updating documentation
- ❌ Moving to next slice before completing current
- ❌ Not following naming conventions
- ❌ Committing without testing
---
## ✅ Quality Gates
Before moving to the next slice, ensure:
1. **All Features Complete**
- All user stories implemented
- All acceptance criteria met
- All API endpoints working
2. **All Tests Pass**
- Backend unit tests
- Backend integration tests
- Frontend manual testing
- Security testing (vendor isolation)
3. **Documentation Updated**
- Slice documentation current
- API docs updated
- Testing checklist completed
4. **Code Quality**
- Follows naming conventions
- No console errors
- No security vulnerabilities
- Performance acceptable
5. **Stakeholder Approval**
- Demo completed
- Feedback incorporated
- Sign-off received
---
## 🎉 Success Metrics
### After Slice 1
- ✅ Admin can create vendors
- ✅ Vendors can log in
- ✅ Vendor isolation works
- ✅ Context detection works
### After Slice 2
- ✅ Vendors can import CSVs
- ✅ Background processing works
- ✅ Import tracking functional
### After Slice 3
- ✅ Products published to catalog
- ✅ Inventory management working
- ✅ Product customization enabled
### After Slice 4
- ✅ Customers can browse products
- ✅ Shopping cart functional
- ✅ Customer accounts working
### After Slice 5
- ✅ Complete checkout workflow
- ✅ Order management operational
- ✅ **Platform production-ready!**
---
## 🚀 Ready to Start?
### Current Focus: Complete Slice 1
**Your immediate next steps:**
1. ✅ Read `01_slice1_admin_vendor_foundation.md`
2. ⏳ Complete vendor login page (`templates/vendor/login.html`)
3. ⏳ Complete vendor dashboard (`templates/vendor/dashboard.html`)
4. ⏳ Test complete admin → vendor flow
5. ⏳ Check all items in Slice 1 testing checklist
6. ⏳ Deploy to staging
7. ⏳ Demo to stakeholders
8. ✅ Move to Slice 2
### Need Help?
- Check the slice documentation for detailed implementation
- Review Alpine.js examples in the docs
- Look at CSS guides for styling
- Test frequently and incrementally
- Update documentation as you progress
---
## 💡 Pro Tips
1. **Work Incrementally**: Complete one component at a time
2. **Test Continuously**: Don't wait until the end to test
3. **Follow Patterns**: Use the examples in slice documentation
4. **Document as You Go**: Update docs while code is fresh
5. **Ask for Reviews**: Get feedback early and often
6. **Celebrate Progress**: Each completed slice is a milestone!
---
## 📞 Support
If you need assistance:
- Review the slice-specific documentation
- Check the testing checklists
- Look at the example code provided
- Refer to the technology stack documentation
---
**Ready to build an amazing multi-tenant ecommerce platform?**
**Start with**: `01_slice1_admin_vendor_foundation.md`
**You've got this!** 🚀

File diff suppressed because it is too large Load Diff

View File

@@ -1,808 +0,0 @@
# Slice 2: Marketplace Product Import
## Vendor Imports Products from Letzshop
**Status**: 📋 NOT STARTED
**Timeline**: Week 2 (5 days)
**Prerequisites**: Slice 1 complete
## 🎯 Slice Objectives
Enable vendors to import product catalogs from Letzshop marketplace via CSV files.
### User Stories
- As a Vendor Owner, I can configure my Letzshop CSV URL
- As a Vendor Owner, I can trigger product imports from Letzshop
- As a Vendor Owner, I can view import job status and history
- The system processes CSV data in the background
- As a Vendor Owner, I can see real-time import progress
### Success Criteria
- [ ] Vendor can configure Letzshop CSV URL (FR, EN, DE)
- [ ] Vendor can trigger import jobs manually
- [ ] System downloads and processes CSV files
- [ ] Import status updates in real-time (Alpine.js)
- [ ] Import history is properly tracked
- [ ] Error handling for failed imports
- [ ] Products stored in staging area (MarketplaceProduct table)
- [ ] Large CSV files process without timeout
## 📋 Backend Implementation
### Database Models
#### MarketplaceProduct Model (`models/database/marketplace_product.py`)
```python
class MarketplaceProduct(Base, TimestampMixin):
"""
Staging table for imported marketplace products
Products stay here until vendor publishes them to catalog
"""
__tablename__ = "marketplace_products"
id = Column(Integer, primary_key=True, index=True)
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
import_job_id = Column(Integer, ForeignKey("marketplace_import_jobs.id"))
# External identifiers
external_sku = Column(String, nullable=False, index=True)
marketplace = Column(String, default="letzshop") # Future: other marketplaces
# Product information (from CSV)
title = Column(String, nullable=False)
description = Column(Text)
price = Column(Numeric(10, 2))
currency = Column(String(3), default="EUR")
# Categories and attributes
category = Column(String)
brand = Column(String)
attributes = Column(JSON, default=dict) # Store all CSV columns
# Images
image_urls = Column(JSON, default=list) # List of image URLs
# Inventory
stock_quantity = Column(Integer)
is_in_stock = Column(Boolean, default=True)
# Status
is_selected = Column(Boolean, default=False) # Ready to publish?
is_published = Column(Boolean, default=False) # Already in catalog?
published_product_id = Column(Integer, ForeignKey("products.id"), nullable=True)
# Metadata
language = Column(String(2)) # 'fr', 'en', 'de'
raw_data = Column(JSON) # Store complete CSV row
# Relationships
vendor = relationship("Vendor", back_populates="marketplace_products")
import_job = relationship("MarketplaceImportJob", back_populates="products")
published_product = relationship("Product", back_populates="marketplace_source")
# Indexes
__table_args__ = (
Index('ix_marketplace_vendor_sku', 'vendor_id', 'external_sku'),
Index('ix_marketplace_selected', 'vendor_id', 'is_selected'),
)
```
#### MarketplaceImportJob Model (`models/database/marketplace.py`)
```python
class MarketplaceImportJob(Base, TimestampMixin):
"""Track CSV import jobs"""
__tablename__ = "marketplace_import_jobs"
id = Column(Integer, primary_key=True, index=True)
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
# Job details
marketplace = Column(String, default="letzshop")
csv_url = Column(String, nullable=False)
language = Column(String(2)) # 'fr', 'en', 'de'
# Status tracking
status = Column(
String,
default="pending"
) # pending, processing, completed, failed
# Progress
total_rows = Column(Integer, default=0)
processed_rows = Column(Integer, default=0)
imported_count = Column(Integer, default=0)
updated_count = Column(Integer, default=0)
error_count = Column(Integer, default=0)
# Timing
started_at = Column(DateTime, nullable=True)
completed_at = Column(DateTime, nullable=True)
# Error handling
error_message = Column(Text, nullable=True)
error_details = Column(JSON, nullable=True)
# Relationships
vendor = relationship("Vendor", back_populates="import_jobs")
products = relationship("MarketplaceProduct", back_populates="import_job")
```
### Pydantic Schemas
#### Import Schemas (`models/schema/marketplace.py`)
```python
from pydantic import BaseModel, HttpUrl
from typing import Optional, List
from datetime import datetime
class MarketplaceImportCreate(BaseModel):
"""Create new import job"""
csv_url: HttpUrl
language: str = Field(..., regex="^(fr|en|de)$")
marketplace: str = "letzshop"
class MarketplaceImportJobResponse(BaseModel):
"""Import job details"""
id: int
vendor_id: int
marketplace: str
csv_url: str
language: str
status: str
total_rows: int
processed_rows: int
imported_count: int
updated_count: int
error_count: int
started_at: Optional[datetime]
completed_at: Optional[datetime]
error_message: Optional[str]
created_at: datetime
class Config:
from_attributes = True
class MarketplaceProductResponse(BaseModel):
"""Marketplace product in staging"""
id: int
vendor_id: int
external_sku: str
title: str
description: Optional[str]
price: float
currency: str
category: Optional[str]
brand: Optional[str]
stock_quantity: Optional[int]
is_in_stock: bool
is_selected: bool
is_published: bool
image_urls: List[str]
language: str
created_at: datetime
class Config:
from_attributes = True
```
### Service Layer
#### Marketplace Service (`app/services/marketplace_service.py`)
```python
from typing import List, Dict, Any
import csv
import requests
from io import StringIO
from sqlalchemy.orm import Session
from models.database.marketplace_product import MarketplaceProduct
from models.database.marketplace import MarketplaceImportJob
class MarketplaceService:
"""Handle marketplace product imports"""
async def create_import_job(
self,
vendor_id: int,
csv_url: str,
language: str,
db: Session
) -> MarketplaceImportJob:
"""Create new import job and start processing"""
# Create job record
job = MarketplaceImportJob(
vendor_id=vendor_id,
csv_url=csv_url,
language=language,
marketplace="letzshop",
status="pending"
)
db.add(job)
db.commit()
db.refresh(job)
# Trigger background processing
from tasks.marketplace_import import process_csv_import
process_csv_import.delay(job.id)
return job
def process_csv_import(self, job_id: int, db: Session):
"""
Process CSV import (called by Celery task)
This is a long-running operation
"""
job = db.query(MarketplaceImportJob).get(job_id)
if not job:
return
try:
# Update status
job.status = "processing"
job.started_at = datetime.utcnow()
db.commit()
# Download CSV
response = requests.get(job.csv_url, timeout=30)
response.raise_for_status()
# Parse CSV
csv_content = StringIO(response.text)
reader = csv.DictReader(csv_content)
# Count total rows
rows = list(reader)
job.total_rows = len(rows)
db.commit()
# Process each row
for idx, row in enumerate(rows):
try:
self._process_csv_row(job, row, db)
job.processed_rows = idx + 1
# Commit every 100 rows
if idx % 100 == 0:
db.commit()
except Exception as e:
job.error_count += 1
# Log error but continue
# Final commit
job.status = "completed"
job.completed_at = datetime.utcnow()
db.commit()
except Exception as e:
job.status = "failed"
job.error_message = str(e)
job.completed_at = datetime.utcnow()
db.commit()
def _process_csv_row(
self,
job: MarketplaceImportJob,
row: Dict[str, Any],
db: Session
):
"""Process single CSV row"""
# Extract fields from CSV
external_sku = row.get('SKU') or row.get('sku')
if not external_sku:
raise ValueError("Missing SKU in CSV row")
# Check if product already exists
existing = db.query(MarketplaceProduct).filter(
MarketplaceProduct.vendor_id == job.vendor_id,
MarketplaceProduct.external_sku == external_sku
).first()
# Parse image URLs
image_urls = []
for i in range(1, 6): # Support up to 5 images
img_url = row.get(f'Image{i}') or row.get(f'image_{i}')
if img_url:
image_urls.append(img_url)
if existing:
# Update existing product
existing.title = row.get('Title') or row.get('title')
existing.description = row.get('Description')
existing.price = float(row.get('Price', 0))
existing.stock_quantity = int(row.get('Stock', 0))
existing.is_in_stock = existing.stock_quantity > 0
existing.category = row.get('Category')
existing.brand = row.get('Brand')
existing.image_urls = image_urls
existing.raw_data = row
existing.import_job_id = job.id
job.updated_count += 1
else:
# Create new product
product = MarketplaceProduct(
vendor_id=job.vendor_id,
import_job_id=job.id,
external_sku=external_sku,
marketplace="letzshop",
title=row.get('Title') or row.get('title'),
description=row.get('Description'),
price=float(row.get('Price', 0)),
currency="EUR",
category=row.get('Category'),
brand=row.get('Brand'),
stock_quantity=int(row.get('Stock', 0)),
is_in_stock=int(row.get('Stock', 0)) > 0,
image_urls=image_urls,
language=job.language,
raw_data=row,
is_selected=False,
is_published=False
)
db.add(product)
job.imported_count += 1
def get_import_jobs(
self,
vendor_id: int,
db: Session,
skip: int = 0,
limit: int = 20
) -> List[MarketplaceImportJob]:
"""Get import job history for vendor"""
return db.query(MarketplaceImportJob).filter(
MarketplaceImportJob.vendor_id == vendor_id
).order_by(
MarketplaceImportJob.created_at.desc()
).offset(skip).limit(limit).all()
def get_marketplace_products(
self,
vendor_id: int,
db: Session,
import_job_id: Optional[int] = None,
is_selected: Optional[bool] = None,
skip: int = 0,
limit: int = 100
) -> List[MarketplaceProduct]:
"""Get marketplace products in staging"""
query = db.query(MarketplaceProduct).filter(
MarketplaceProduct.vendor_id == vendor_id,
MarketplaceProduct.is_published == False # Only unpublished
)
if import_job_id:
query = query.filter(MarketplaceProduct.import_job_id == import_job_id)
if is_selected is not None:
query = query.filter(MarketplaceProduct.is_selected == is_selected)
return query.order_by(
MarketplaceProduct.created_at.desc()
).offset(skip).limit(limit).all()
```
### API Endpoints
#### Marketplace Endpoints (`app/api/v1/vendor/marketplace.py`)
```python
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List, Optional
router = APIRouter()
@router.post("/import", response_model=MarketplaceImportJobResponse)
async def trigger_import(
import_data: MarketplaceImportCreate,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Trigger CSV import from marketplace"""
service = MarketplaceService()
job = await service.create_import_job(
vendor_id=vendor.id,
csv_url=str(import_data.csv_url),
language=import_data.language,
db=db
)
return job
@router.get("/jobs", response_model=List[MarketplaceImportJobResponse])
async def get_import_jobs(
skip: int = 0,
limit: int = 20,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Get import job history"""
service = MarketplaceService()
jobs = service.get_import_jobs(vendor.id, db, skip, limit)
return jobs
@router.get("/jobs/{job_id}", response_model=MarketplaceImportJobResponse)
async def get_import_job(
job_id: int,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Get specific import job status"""
job = db.query(MarketplaceImportJob).filter(
MarketplaceImportJob.id == job_id,
MarketplaceImportJob.vendor_id == vendor.id
).first()
if not job:
raise HTTPException(status_code=404, detail="Import job not found")
return job
@router.get("/products", response_model=List[MarketplaceProductResponse])
async def get_marketplace_products(
import_job_id: Optional[int] = None,
is_selected: Optional[bool] = None,
skip: int = 0,
limit: int = 100,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Get products in marketplace staging area"""
service = MarketplaceService()
products = service.get_marketplace_products(
vendor.id, db, import_job_id, is_selected, skip, limit
)
return products
```
### Background Tasks
#### Celery Task (`tasks/marketplace_import.py`)
```python
from celery import shared_task
from app.core.database import SessionLocal
from app.services.marketplace_service import MarketplaceService
@shared_task(bind=True, max_retries=3)
def process_csv_import(self, job_id: int):
"""
Process CSV import in background
This can take several minutes for large files
"""
db = SessionLocal()
try:
service = MarketplaceService()
service.process_csv_import(job_id, db)
except Exception as e:
# Retry on failure
raise self.retry(exc=e, countdown=60)
finally:
db.close()
```
## 🎨 Frontend Implementation
### Templates
#### Import Dashboard (`templates/vendor/marketplace/imports.html`)
```html
{% extends "vendor/base_vendor.html" %}
{% block title %}Product Import{% endblock %}
{% block content %}
<div x-data="marketplaceImport()" x-init="loadJobs()">
<!-- Page Header -->
<div class="page-header">
<h1>Marketplace Import</h1>
<button @click="showImportModal = true" class="btn btn-primary">
New Import
</button>
</div>
<!-- Import Configuration Card -->
<div class="card mb-3">
<h3>Letzshop CSV URLs</h3>
<div class="config-grid">
<div>
<label>French (FR)</label>
<input
type="text"
x-model="vendor.letzshop_csv_url_fr"
class="form-control"
readonly
>
</div>
<div>
<label>English (EN)</label>
<input
type="text"
x-model="vendor.letzshop_csv_url_en"
class="form-control"
readonly
>
</div>
<div>
<label>German (DE)</label>
<input
type="text"
x-model="vendor.letzshop_csv_url_de"
class="form-control"
readonly
>
</div>
</div>
<p class="text-muted mt-2">
Configure these URLs in vendor settings
</p>
</div>
<!-- Import Jobs List -->
<div class="card">
<h3>Import History</h3>
<template x-if="jobs.length === 0 && !loading">
<p class="text-muted">No imports yet. Start your first import!</p>
</template>
<template x-if="jobs.length > 0">
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Language</th>
<th>Status</th>
<th>Progress</th>
<th>Results</th>
<th>Started</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<template x-for="job in jobs" :key="job.id">
<tr>
<td><strong x-text="`#${job.id}`"></strong></td>
<td x-text="job.language.toUpperCase()"></td>
<td>
<span
class="badge"
:class="{
'badge-warning': job.status === 'pending' || job.status === 'processing',
'badge-success': job.status === 'completed',
'badge-danger': job.status === 'failed'
}"
x-text="job.status"
></span>
</td>
<td>
<template x-if="job.status === 'processing'">
<div class="progress-bar">
<div
class="progress-fill"
:style="`width: ${(job.processed_rows / job.total_rows * 100)}%`"
></div>
</div>
<small x-text="`${job.processed_rows} / ${job.total_rows}`"></small>
</template>
<template x-if="job.status === 'completed'">
<span x-text="`${job.total_rows} rows`"></span>
</template>
</td>
<td>
<template x-if="job.status === 'completed'">
<div class="text-sm">
<div><span x-text="job.imported_count"></span> imported</div>
<div><span x-text="job.updated_count"></span> updated</div>
<template x-if="job.error_count > 0">
<div class="text-danger"><span x-text="job.error_count"></span> errors</div>
</template>
</div>
</template>
</td>
<td x-text="formatDate(job.started_at)"></td>
<td>
<button
@click="viewProducts(job.id)"
class="btn btn-sm"
:disabled="job.status !== 'completed'"
>
View Products
</button>
</td>
</tr>
</template>
</tbody>
</table>
</template>
</div>
<!-- New Import Modal -->
<div x-show="showImportModal" class="modal-overlay" @click.self="showImportModal = false">
<div class="modal">
<div class="modal-header">
<h3>New Import</h3>
<button @click="showImportModal = false" class="modal-close">×</button>
</div>
<div class="modal-body">
<form @submit.prevent="triggerImport()">
<div class="form-group">
<label>Language</label>
<select x-model="newImport.language" class="form-control" required>
<option value="">Select language</option>
<option value="fr">French (FR)</option>
<option value="en">English (EN)</option>
<option value="de">German (DE)</option>
</select>
</div>
<div class="form-group">
<label>CSV URL</label>
<input
type="url"
x-model="newImport.csv_url"
class="form-control"
placeholder="https://..."
required
>
<div class="form-help">
Or use configured URL:
<button
type="button"
@click="newImport.csv_url = vendor.letzshop_csv_url_fr"
class="btn-link"
x-show="newImport.language === 'fr'"
>
Use FR URL
</button>
</div>
</div>
<div class="modal-footer">
<button type="button" @click="showImportModal = false" class="btn btn-secondary">
Cancel
</button>
<button type="submit" class="btn btn-primary" :disabled="importing">
<span x-show="!importing">Start Import</span>
<span x-show="importing" class="loading-spinner"></span>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
window.vendorData = {{ vendor|tojson }};
</script>
{% endblock %}
{% block extra_scripts %}
<script>
function marketplaceImport() {
return {
vendor: window.vendorData,
jobs: [],
loading: false,
importing: false,
showImportModal: false,
newImport: {
language: '',
csv_url: ''
},
async loadJobs() {
this.loading = true;
try {
this.jobs = await apiClient.get('/api/v1/vendor/marketplace/jobs');
// Poll for active jobs
const activeJobs = this.jobs.filter(j =>
j.status === 'pending' || j.status === 'processing'
);
if (activeJobs.length > 0) {
setTimeout(() => this.loadJobs(), 3000); // Poll every 3 seconds
}
} catch (error) {
showNotification('Failed to load imports', 'error');
} finally {
this.loading = false;
}
},
async triggerImport() {
this.importing = true;
try {
const job = await apiClient.post('/api/v1/vendor/marketplace/import', {
csv_url: this.newImport.csv_url,
language: this.newImport.language
});
this.jobs.unshift(job);
this.showImportModal = false;
this.newImport = { language: '', csv_url: '' };
showNotification('Import started successfully', 'success');
// Start polling
setTimeout(() => this.loadJobs(), 3000);
} catch (error) {
showNotification(error.message || 'Import failed', 'error');
} finally {
this.importing = false;
}
},
viewProducts(jobId) {
window.location.href = `/vendor/marketplace/products?job_id=${jobId}`;
},
formatDate(dateString) {
if (!dateString) return '-';
return new Date(dateString).toLocaleString();
}
}
}
</script>
{% endblock %}
```
## ✅ Testing Checklist
### Backend Tests
- [ ] CSV download works with valid URL
- [ ] CSV parsing handles various formats
- [ ] Products created in staging table
- [ ] Duplicate SKUs are updated, not duplicated
- [ ] Import job status updates correctly
- [ ] Progress tracking is accurate
- [ ] Error handling works for invalid CSV
- [ ] Large CSV files (10,000+ rows) process successfully
- [ ] Celery tasks execute correctly
### Frontend Tests
- [ ] Import page loads correctly
- [ ] New import modal works
- [ ] Import jobs display in table
- [ ] Real-time progress updates (polling)
- [ ] Completed imports show results
- [ ] Can view products from import
- [ ] Error states display correctly
- [ ] Loading states work correctly
### Integration Tests
- [ ] Complete import workflow works end-to-end
- [ ] Vendor isolation maintained
- [ ] API endpoints require authentication
- [ ] Vendor can only see their own imports
## 🚀 Deployment Checklist
- [ ] Celery worker running
- [ ] Redis/RabbitMQ configured for Celery
- [ ] Database migrations applied
- [ ] CSV download timeout configured
- [ ] Error logging configured
- [ ] Background task monitoring set up
## ➡️ Next Steps
After completing Slice 2, move to **Slice 3: Product Catalog Management** to enable vendors to publish imported products to their catalog.
---
**Slice 2 Status**: 📋 Not Started
**Dependencies**: Slice 1 must be complete
**Estimated Duration**: 5 days

View File

@@ -1,624 +0,0 @@
# Slice 3: Product Catalog Management
## Vendor Selects and Publishes Products
**Status**: 📋 NOT STARTED
**Timeline**: Week 3 (5 days)
**Prerequisites**: Slice 1 & 2 complete
## 🎯 Slice Objectives
Enable vendors to browse imported products, select which to publish, customize them, and manage their product catalog.
### User Stories
- As a Vendor Owner, I can browse imported products from staging
- As a Vendor Owner, I can select which products to publish to my catalog
- As a Vendor Owner, I can customize product information (pricing, descriptions)
- As a Vendor Owner, I can manage my published product catalog
- As a Vendor Owner, I can manually add products (not from marketplace)
- As a Vendor Owner, I can manage inventory for catalog products
### Success Criteria
- [ ] Vendor can browse all imported products in staging
- [ ] Vendor can filter/search staging products
- [ ] Vendor can select products for publishing
- [ ] Vendor can customize product details before/after publishing
- [ ] Published products appear in vendor catalog
- [ ] Vendor can manually create products
- [ ] Vendor can update product inventory
- [ ] Vendor can activate/deactivate products
- [ ] Product operations are properly isolated by vendor
## 📋 Backend Implementation
### Database Models
#### Product Model (`models/database/product.py`)
```python
class Product(Base, TimestampMixin):
"""
Vendor's published product catalog
These are customer-facing products
"""
__tablename__ = "products"
id = Column(Integer, primary_key=True, index=True)
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
# Basic information
sku = Column(String, nullable=False, index=True)
title = Column(String, nullable=False)
description = Column(Text)
short_description = Column(String(500))
# Pricing
price = Column(Numeric(10, 2), nullable=False)
compare_at_price = Column(Numeric(10, 2)) # Original price for discounts
cost_per_item = Column(Numeric(10, 2)) # For profit tracking
currency = Column(String(3), default="EUR")
# Categorization
category = Column(String)
subcategory = Column(String)
brand = Column(String)
tags = Column(JSON, default=list)
# SEO
slug = Column(String, unique=True, index=True)
meta_title = Column(String)
meta_description = Column(String)
# Images
featured_image = Column(String) # Main product image
image_urls = Column(JSON, default=list) # Additional images
# Status
is_active = Column(Boolean, default=True)
is_featured = Column(Boolean, default=False)
is_on_sale = Column(Boolean, default=False)
# Inventory (simple - detailed in Inventory model)
track_inventory = Column(Boolean, default=True)
stock_quantity = Column(Integer, default=0)
low_stock_threshold = Column(Integer, default=10)
# Marketplace source (if imported)
marketplace_product_id = Column(
Integer,
ForeignKey("marketplace_products.id"),
nullable=True
)
external_sku = Column(String, nullable=True) # Original marketplace SKU
# Additional data
attributes = Column(JSON, default=dict) # Custom attributes
weight = Column(Numeric(10, 2)) # For shipping
dimensions = Column(JSON) # {length, width, height}
# Relationships
vendor = relationship("Vendor", back_populates="products")
marketplace_source = relationship(
"MarketplaceProduct",
back_populates="published_product"
)
inventory_records = relationship("Inventory", back_populates="product")
order_items = relationship("OrderItem", back_populates="product")
# Indexes
__table_args__ = (
Index('ix_product_vendor_sku', 'vendor_id', 'sku'),
Index('ix_product_active', 'vendor_id', 'is_active'),
Index('ix_product_featured', 'vendor_id', 'is_featured'),
)
```
#### Inventory Model (`models/database/inventory.py`)
```python
class Inventory(Base, TimestampMixin):
"""Track product inventory by location"""
__tablename__ = "inventory"
id = Column(Integer, primary_key=True, index=True)
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False)
# Location
location_name = Column(String, default="Default") # Warehouse name
# Quantities
available_quantity = Column(Integer, default=0)
reserved_quantity = Column(Integer, default=0) # Pending orders
# Relationships
vendor = relationship("Vendor")
product = relationship("Product", back_populates="inventory_records")
movements = relationship("InventoryMovement", back_populates="inventory")
class InventoryMovement(Base, TimestampMixin):
"""Track inventory changes"""
__tablename__ = "inventory_movements"
id = Column(Integer, primary_key=True, index=True)
inventory_id = Column(Integer, ForeignKey("inventory.id"), nullable=False)
# Movement details
movement_type = Column(String) # 'received', 'sold', 'adjusted', 'returned'
quantity_change = Column(Integer) # Positive or negative
# Context
reference_type = Column(String, nullable=True) # 'order', 'import', 'manual'
reference_id = Column(Integer, nullable=True)
notes = Column(Text)
# Relationships
inventory = relationship("Inventory", back_populates="movements")
```
### Pydantic Schemas
#### Product Schemas (`models/schema/product.py`)
```python
class ProductCreate(BaseModel):
"""Create product from scratch"""
sku: str
title: str
description: Optional[str] = None
price: float = Field(..., gt=0)
compare_at_price: Optional[float] = None
cost_per_item: Optional[float] = None
category: Optional[str] = None
brand: Optional[str] = None
tags: List[str] = []
image_urls: List[str] = []
track_inventory: bool = True
stock_quantity: int = 0
is_active: bool = True
class ProductPublishFromMarketplace(BaseModel):
"""Publish product from marketplace staging"""
marketplace_product_id: int
custom_title: Optional[str] = None
custom_description: Optional[str] = None
custom_price: Optional[float] = None
custom_sku: Optional[str] = None
stock_quantity: int = 0
is_active: bool = True
class ProductUpdate(BaseModel):
"""Update existing product"""
title: Optional[str] = None
description: Optional[str] = None
short_description: Optional[str] = None
price: Optional[float] = None
compare_at_price: Optional[float] = None
category: Optional[str] = None
brand: Optional[str] = None
tags: Optional[List[str]] = None
image_urls: Optional[List[str]] = None
is_active: Optional[bool] = None
is_featured: Optional[bool] = None
stock_quantity: Optional[int] = None
class ProductResponse(BaseModel):
"""Product details"""
id: int
vendor_id: int
sku: str
title: str
description: Optional[str]
price: float
compare_at_price: Optional[float]
category: Optional[str]
brand: Optional[str]
tags: List[str]
featured_image: Optional[str]
image_urls: List[str]
is_active: bool
is_featured: bool
stock_quantity: int
marketplace_product_id: Optional[int]
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
```
### Service Layer
#### Product Service (`app/services/product_service.py`)
```python
class ProductService:
"""Handle product catalog operations"""
async def publish_from_marketplace(
self,
vendor_id: int,
publish_data: ProductPublishFromMarketplace,
db: Session
) -> Product:
"""Publish marketplace product to catalog"""
# Get marketplace product
mp_product = db.query(MarketplaceProduct).filter(
MarketplaceProduct.id == publish_data.marketplace_product_id,
MarketplaceProduct.vendor_id == vendor_id,
MarketplaceProduct.is_published == False
).first()
if not mp_product:
raise ProductNotFoundError("Marketplace product not found")
# Check if SKU already exists
sku = publish_data.custom_sku or mp_product.external_sku
existing = db.query(Product).filter(
Product.vendor_id == vendor_id,
Product.sku == sku
).first()
if existing:
raise ProductAlreadyExistsError(f"Product with SKU {sku} already exists")
# Create product
product = Product(
vendor_id=vendor_id,
sku=sku,
title=publish_data.custom_title or mp_product.title,
description=publish_data.custom_description or mp_product.description,
price=publish_data.custom_price or mp_product.price,
currency=mp_product.currency,
category=mp_product.category,
brand=mp_product.brand,
image_urls=mp_product.image_urls,
featured_image=mp_product.image_urls[0] if mp_product.image_urls else None,
marketplace_product_id=mp_product.id,
external_sku=mp_product.external_sku,
stock_quantity=publish_data.stock_quantity,
is_active=publish_data.is_active,
slug=self._generate_slug(publish_data.custom_title or mp_product.title)
)
db.add(product)
# Mark marketplace product as published
mp_product.is_published = True
mp_product.published_product_id = product.id
# Create initial inventory record
inventory = Inventory(
vendor_id=vendor_id,
product_id=product.id,
location_name="Default",
available_quantity=publish_data.stock_quantity,
reserved_quantity=0
)
db.add(inventory)
# Record inventory movement
if publish_data.stock_quantity > 0:
movement = InventoryMovement(
inventory_id=inventory.id,
movement_type="received",
quantity_change=publish_data.stock_quantity,
reference_type="import",
notes="Initial stock from marketplace import"
)
db.add(movement)
db.commit()
db.refresh(product)
return product
async def create_product(
self,
vendor_id: int,
product_data: ProductCreate,
db: Session
) -> Product:
"""Create product manually"""
# Check SKU uniqueness
existing = db.query(Product).filter(
Product.vendor_id == vendor_id,
Product.sku == product_data.sku
).first()
if existing:
raise ProductAlreadyExistsError(f"SKU {product_data.sku} already exists")
product = Product(
vendor_id=vendor_id,
**product_data.dict(),
slug=self._generate_slug(product_data.title),
featured_image=product_data.image_urls[0] if product_data.image_urls else None
)
db.add(product)
# Create inventory
if product_data.track_inventory:
inventory = Inventory(
vendor_id=vendor_id,
product_id=product.id,
available_quantity=product_data.stock_quantity
)
db.add(inventory)
db.commit()
db.refresh(product)
return product
def get_products(
self,
vendor_id: int,
db: Session,
is_active: Optional[bool] = None,
category: Optional[str] = None,
search: Optional[str] = None,
skip: int = 0,
limit: int = 100
) -> List[Product]:
"""Get vendor's product catalog"""
query = db.query(Product).filter(Product.vendor_id == vendor_id)
if is_active is not None:
query = query.filter(Product.is_active == is_active)
if category:
query = query.filter(Product.category == category)
if search:
query = query.filter(
or_(
Product.title.ilike(f"%{search}%"),
Product.sku.ilike(f"%{search}%"),
Product.brand.ilike(f"%{search}%")
)
)
return query.order_by(
Product.created_at.desc()
).offset(skip).limit(limit).all()
async def update_product(
self,
vendor_id: int,
product_id: int,
update_data: ProductUpdate,
db: Session
) -> Product:
"""Update product details"""
product = db.query(Product).filter(
Product.id == product_id,
Product.vendor_id == vendor_id
).first()
if not product:
raise ProductNotFoundError()
# Update fields
update_dict = update_data.dict(exclude_unset=True)
for field, value in update_dict.items():
setattr(product, field, value)
# Update stock if changed
if 'stock_quantity' in update_dict:
self._update_inventory(product, update_dict['stock_quantity'], db)
db.commit()
db.refresh(product)
return product
def _generate_slug(self, title: str) -> str:
"""Generate URL-friendly slug"""
import re
slug = title.lower()
slug = re.sub(r'[^a-z0-9]+', '-', slug)
slug = slug.strip('-')
return slug
def _update_inventory(
self,
product: Product,
new_quantity: int,
db: Session
):
"""Update product inventory"""
inventory = db.query(Inventory).filter(
Inventory.product_id == product.id
).first()
if inventory:
quantity_change = new_quantity - inventory.available_quantity
inventory.available_quantity = new_quantity
# Record movement
movement = InventoryMovement(
inventory_id=inventory.id,
movement_type="adjusted",
quantity_change=quantity_change,
reference_type="manual",
notes="Manual adjustment"
)
db.add(movement)
```
### API Endpoints
#### Product Endpoints (`app/api/v1/vendor/products.py`)
```python
@router.get("", response_model=List[ProductResponse])
async def get_products(
is_active: Optional[bool] = None,
category: Optional[str] = None,
search: Optional[str] = None,
skip: int = 0,
limit: int = 100,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Get vendor's product catalog"""
service = ProductService()
products = service.get_products(
vendor.id, db, is_active, category, search, skip, limit
)
return products
@router.post("", response_model=ProductResponse)
async def create_product(
product_data: ProductCreate,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Create product manually"""
service = ProductService()
product = await service.create_product(vendor.id, product_data, db)
return product
@router.post("/from-marketplace", response_model=ProductResponse)
async def publish_from_marketplace(
publish_data: ProductPublishFromMarketplace,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Publish marketplace product to catalog"""
service = ProductService()
product = await service.publish_from_marketplace(
vendor.id, publish_data, db
)
return product
@router.get("/{product_id}", response_model=ProductResponse)
async def get_product(
product_id: int,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Get product details"""
product = db.query(Product).filter(
Product.id == product_id,
Product.vendor_id == vendor.id
).first()
if not product:
raise HTTPException(status_code=404, detail="Product not found")
return product
@router.put("/{product_id}", response_model=ProductResponse)
async def update_product(
product_id: int,
update_data: ProductUpdate,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Update product"""
service = ProductService()
product = await service.update_product(vendor.id, product_id, update_data, db)
return product
@router.put("/{product_id}/toggle-active")
async def toggle_product_active(
product_id: int,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Activate/deactivate product"""
product = db.query(Product).filter(
Product.id == product_id,
Product.vendor_id == vendor.id
).first()
if not product:
raise HTTPException(status_code=404, detail="Product not found")
product.is_active = not product.is_active
db.commit()
return {"is_active": product.is_active}
@router.delete("/{product_id}")
async def delete_product(
product_id: int,
current_user: User = Depends(get_current_vendor_user),
vendor: Vendor = Depends(get_current_vendor),
db: Session = Depends(get_db)
):
"""Remove product from catalog"""
product = db.query(Product).filter(
Product.id == product_id,
Product.vendor_id == vendor.id
).first()
if not product:
raise HTTPException(status_code=404, detail="Product not found")
# Mark as inactive instead of deleting
product.is_active = False
db.commit()
return {"success": True}
```
## 🎨 Frontend Implementation
### Templates
#### Browse Marketplace Products (`templates/vendor/marketplace/browse.html`)
Uses Alpine.js for reactive filtering, selection, and bulk publishing.
#### Product Catalog (`templates/vendor/products/list.html`)
Product management interface with search, filters, and quick actions.
#### Product Edit (`templates/vendor/products/edit.html`)
Detailed product editing with image management and inventory tracking.
## ✅ Testing Checklist
### Backend Tests
- [ ] Product publishing from marketplace works
- [ ] Manual product creation works
- [ ] Product updates work correctly
- [ ] Inventory tracking is accurate
- [ ] SKU uniqueness is enforced
- [ ] Vendor isolation maintained
- [ ] Product search/filtering works
- [ ] Slug generation works correctly
### Frontend Tests
- [ ] Browse marketplace products
- [ ] Select multiple products for publishing
- [ ] Publish single product with customization
- [ ] View product catalog
- [ ] Edit product details
- [ ] Toggle product active status
- [ ] Delete/deactivate products
- [ ] Search and filter products
## ➡️ Next Steps
After completing Slice 3, move to **Slice 4: Customer Shopping Experience** to build the public-facing shop.
---
**Slice 3 Status**: 📋 Not Started
**Dependencies**: Slices 1 & 2 must be complete
**Estimated Duration**: 5 days

View File

@@ -1,887 +0,0 @@
# Slice 4: Customer Shopping Experience
## Customers Browse and Shop on Vendor Stores
**Status**: 📋 NOT STARTED
**Timeline**: Week 4 (5 days)
**Prerequisites**: Slices 1, 2, & 3 complete
## 🎯 Slice Objectives
Build the public-facing customer shop where customers can browse products, register accounts, and add items to cart.
### User Stories
- As a Customer, I can browse products on a vendor's shop
- As a Customer, I can view detailed product information
- As a Customer, I can search for products
- As a Customer, I can register for a vendor-specific account
- As a Customer, I can log into my account
- As a Customer, I can add products to my shopping cart
- As a Customer, I can manage my cart (update quantities, remove items)
- Cart persists across sessions
### Success Criteria
- [ ] Customers can browse products without authentication
- [ ] Product catalog displays correctly with images and prices
- [ ] Product detail pages show complete information
- [ ] Search functionality works
- [ ] Customers can register vendor-specific accounts
- [ ] Customer login/logout works
- [ ] Shopping cart is functional with Alpine.js reactivity
- [ ] Cart persists (session-based before login, user-based after)
- [ ] Customer data is properly isolated by vendor
- [ ] Mobile responsive design
## 📋 Backend Implementation
### Database Models
#### Customer Model (`models/database/customer.py`)
```python
class Customer(Base, TimestampMixin):
"""
Vendor-scoped customer accounts
Each customer belongs to ONE vendor
"""
__tablename__ = "customers"
id = Column(Integer, primary_key=True, index=True)
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
# Authentication
email = Column(String, nullable=False, index=True)
hashed_password = Column(String, nullable=False)
# Personal information
first_name = Column(String)
last_name = Column(String)
phone = Column(String)
# Customer metadata
customer_number = Column(String, unique=True, index=True) # Auto-generated
# Preferences
language = Column(String(2), default="en")
newsletter_subscribed = Column(Boolean, default=False)
marketing_emails = Column(Boolean, default=True)
preferences = Column(JSON, default=dict)
# Statistics
total_orders = Column(Integer, default=0)
total_spent = Column(Numeric(10, 2), default=0)
# Status
is_active = Column(Boolean, default=True)
email_verified = Column(Boolean, default=False)
last_login_at = Column(DateTime, nullable=True)
# Relationships
vendor = relationship("Vendor", back_populates="customers")
addresses = relationship("CustomerAddress", back_populates="customer", cascade="all, delete-orphan")
orders = relationship("Order", back_populates="customer")
cart = relationship("Cart", back_populates="customer", uselist=False)
# Indexes
__table_args__ = (
Index('ix_customer_vendor_email', 'vendor_id', 'email', unique=True),
)
class CustomerAddress(Base, TimestampMixin):
"""Customer shipping/billing addresses"""
__tablename__ = "customer_addresses"
id = Column(Integer, primary_key=True, index=True)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
# Address type
address_type = Column(String, default="shipping") # shipping, billing, both
is_default = Column(Boolean, default=False)
# Address details
first_name = Column(String)
last_name = Column(String)
company = Column(String)
address_line1 = Column(String, nullable=False)
address_line2 = Column(String)
city = Column(String, nullable=False)
state_province = Column(String)
postal_code = Column(String, nullable=False)
country = Column(String, nullable=False, default="LU")
phone = Column(String)
# Relationships
customer = relationship("Customer", back_populates="addresses")
```
#### Cart Model (`models/database/cart.py`)
```python
class Cart(Base, TimestampMixin):
"""Shopping cart - session or customer-based"""
__tablename__ = "carts"
id = Column(Integer, primary_key=True, index=True)
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
# Owner (one of these must be set)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=True)
session_id = Column(String, nullable=True, index=True) # For guest users
# Cart metadata
currency = Column(String(3), default="EUR")
# Relationships
vendor = relationship("Vendor")
customer = relationship("Customer", back_populates="cart")
items = relationship("CartItem", back_populates="cart", cascade="all, delete-orphan")
# Computed properties
@property
def total_items(self) -> int:
return sum(item.quantity for item in self.items)
@property
def subtotal(self) -> Decimal:
return sum(item.line_total for item in self.items)
__table_args__ = (
Index('ix_cart_vendor_session', 'vendor_id', 'session_id'),
Index('ix_cart_vendor_customer', 'vendor_id', 'customer_id'),
)
class CartItem(Base, TimestampMixin):
"""Individual items in cart"""
__tablename__ = "cart_items"
id = Column(Integer, primary_key=True, index=True)
cart_id = Column(Integer, ForeignKey("carts.id"), nullable=False)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False)
# Item details
quantity = Column(Integer, nullable=False, default=1)
unit_price = Column(Numeric(10, 2), nullable=False) # Snapshot at time of add
# Relationships
cart = relationship("Cart", back_populates="items")
product = relationship("Product")
@property
def line_total(self) -> Decimal:
return self.unit_price * self.quantity
```
### Pydantic Schemas
#### Customer Schemas (`models/schema/customer.py`)
```python
class CustomerRegister(BaseModel):
"""Customer registration"""
email: EmailStr
password: str = Field(..., min_length=8)
first_name: str
last_name: str
phone: Optional[str] = None
newsletter_subscribed: bool = False
class CustomerLogin(BaseModel):
"""Customer login"""
email: EmailStr
password: str
class CustomerResponse(BaseModel):
"""Customer details"""
id: int
vendor_id: int
email: str
first_name: str
last_name: str
phone: Optional[str]
customer_number: str
total_orders: int
total_spent: float
is_active: bool
created_at: datetime
class Config:
from_attributes = True
class CustomerAddressCreate(BaseModel):
"""Create address"""
address_type: str = "shipping"
is_default: bool = False
first_name: str
last_name: str
company: Optional[str] = None
address_line1: str
address_line2: Optional[str] = None
city: str
state_province: Optional[str] = None
postal_code: str
country: str = "LU"
phone: Optional[str] = None
class CustomerAddressResponse(BaseModel):
"""Address details"""
id: int
address_type: str
is_default: bool
first_name: str
last_name: str
company: Optional[str]
address_line1: str
address_line2: Optional[str]
city: str
state_province: Optional[str]
postal_code: str
country: str
phone: Optional[str]
class Config:
from_attributes = True
```
#### Cart Schemas (`models/schema/cart.py`)
```python
class CartItemAdd(BaseModel):
"""Add item to cart"""
product_id: int
quantity: int = Field(..., gt=0)
class CartItemUpdate(BaseModel):
"""Update cart item"""
quantity: int = Field(..., gt=0)
class CartItemResponse(BaseModel):
"""Cart item details"""
id: int
product_id: int
product_title: str
product_image: Optional[str]
product_sku: str
quantity: int
unit_price: float
line_total: float
class Config:
from_attributes = True
class CartResponse(BaseModel):
"""Complete cart"""
id: int
vendor_id: int
total_items: int
subtotal: float
currency: str
items: List[CartItemResponse]
class Config:
from_attributes = True
```
### Service Layer
#### Customer Service (`app/services/customer_service.py`)
```python
class CustomerService:
"""Handle customer operations"""
def __init__(self):
self.auth_manager = AuthManager()
async def register_customer(
self,
vendor_id: int,
customer_data: CustomerRegister,
db: Session
) -> Customer:
"""Register new customer for vendor"""
# Check if email already exists for this vendor
existing = db.query(Customer).filter(
Customer.vendor_id == vendor_id,
Customer.email == customer_data.email
).first()
if existing:
raise CustomerAlreadyExistsError("Email already registered")
# Generate customer number
customer_number = self._generate_customer_number(vendor_id, db)
# Create customer
customer = Customer(
vendor_id=vendor_id,
email=customer_data.email,
hashed_password=self.auth_manager.hash_password(customer_data.password),
first_name=customer_data.first_name,
last_name=customer_data.last_name,
phone=customer_data.phone,
customer_number=customer_number,
newsletter_subscribed=customer_data.newsletter_subscribed,
is_active=True
)
db.add(customer)
db.commit()
db.refresh(customer)
return customer
async def authenticate_customer(
self,
vendor_id: int,
email: str,
password: str,
db: Session
) -> Tuple[Customer, str]:
"""Authenticate customer and return token"""
customer = db.query(Customer).filter(
Customer.vendor_id == vendor_id,
Customer.email == email
).first()
if not customer:
raise InvalidCredentialsError()
if not customer.is_active:
raise CustomerInactiveError()
if not self.auth_manager.verify_password(password, customer.hashed_password):
raise InvalidCredentialsError()
# Update last login
customer.last_login_at = datetime.utcnow()
db.commit()
# Generate JWT token
token = self.auth_manager.create_access_token({
"sub": str(customer.id),
"email": customer.email,
"vendor_id": vendor_id,
"type": "customer"
})
return customer, token
def _generate_customer_number(self, vendor_id: int, db: Session) -> str:
"""Generate unique customer number"""
# Format: VENDOR_CODE-YYYYMMDD-XXXX
from models.database.vendor import Vendor
vendor = db.query(Vendor).get(vendor_id)
date_str = datetime.utcnow().strftime("%Y%m%d")
# Count customers today
today_start = datetime.utcnow().replace(hour=0, minute=0, second=0)
count = db.query(Customer).filter(
Customer.vendor_id == vendor_id,
Customer.created_at >= today_start
).count()
return f"{vendor.vendor_code}-{date_str}-{count+1:04d}"
```
#### Cart Service (`app/services/cart_service.py`)
```python
class CartService:
"""Handle shopping cart operations"""
async def get_or_create_cart(
self,
vendor_id: int,
db: Session,
customer_id: Optional[int] = None,
session_id: Optional[str] = None
) -> Cart:
"""Get existing cart or create new one"""
if customer_id:
cart = db.query(Cart).filter(
Cart.vendor_id == vendor_id,
Cart.customer_id == customer_id
).first()
else:
cart = db.query(Cart).filter(
Cart.vendor_id == vendor_id,
Cart.session_id == session_id
).first()
if not cart:
cart = Cart(
vendor_id=vendor_id,
customer_id=customer_id,
session_id=session_id
)
db.add(cart)
db.commit()
db.refresh(cart)
return cart
async def add_to_cart(
self,
cart: Cart,
product_id: int,
quantity: int,
db: Session
) -> CartItem:
"""Add product to cart"""
# Verify product exists and is active
product = db.query(Product).filter(
Product.id == product_id,
Product.vendor_id == cart.vendor_id,
Product.is_active == True
).first()
if not product:
raise ProductNotFoundError()
# Check if product already in cart
existing_item = db.query(CartItem).filter(
CartItem.cart_id == cart.id,
CartItem.product_id == product_id
).first()
if existing_item:
# Update quantity
existing_item.quantity += quantity
db.commit()
db.refresh(existing_item)
return existing_item
else:
# Add new item
cart_item = CartItem(
cart_id=cart.id,
product_id=product_id,
quantity=quantity,
unit_price=product.price
)
db.add(cart_item)
db.commit()
db.refresh(cart_item)
return cart_item
async def update_cart_item(
self,
cart_item_id: int,
quantity: int,
cart: Cart,
db: Session
) -> CartItem:
"""Update cart item quantity"""
cart_item = db.query(CartItem).filter(
CartItem.id == cart_item_id,
CartItem.cart_id == cart.id
).first()
if not cart_item:
raise CartItemNotFoundError()
cart_item.quantity = quantity
db.commit()
db.refresh(cart_item)
return cart_item
async def remove_from_cart(
self,
cart_item_id: int,
cart: Cart,
db: Session
):
"""Remove item from cart"""
cart_item = db.query(CartItem).filter(
CartItem.id == cart_item_id,
CartItem.cart_id == cart.id
).first()
if not cart_item:
raise CartItemNotFoundError()
db.delete(cart_item)
db.commit()
async def clear_cart(self, cart: Cart, db: Session):
"""Clear all items from cart"""
db.query(CartItem).filter(CartItem.cart_id == cart.id).delete()
db.commit()
async def merge_carts(
self,
session_cart_id: int,
customer_cart_id: int,
db: Session
):
"""Merge session cart into customer cart after login"""
session_cart = db.query(Cart).get(session_cart_id)
customer_cart = db.query(Cart).get(customer_cart_id)
if not session_cart or not customer_cart:
return
# Move items from session cart to customer cart
for item in session_cart.items:
# Check if product already in customer cart
existing = db.query(CartItem).filter(
CartItem.cart_id == customer_cart.id,
CartItem.product_id == item.product_id
).first()
if existing:
existing.quantity += item.quantity
else:
item.cart_id = customer_cart.id
# Delete session cart
db.delete(session_cart)
db.commit()
```
### API Endpoints
#### Public Product Endpoints (`app/api/v1/public/vendors/products.py`)
```python
@router.get("", response_model=List[ProductResponse])
async def get_public_products(
vendor_id: int,
category: Optional[str] = None,
search: Optional[str] = None,
min_price: Optional[float] = None,
max_price: Optional[float] = None,
skip: int = 0,
limit: int = 50,
db: Session = Depends(get_db)
):
"""Get public product catalog (no auth required)"""
query = db.query(Product).filter(
Product.vendor_id == vendor_id,
Product.is_active == True
)
if category:
query = query.filter(Product.category == category)
if search:
query = query.filter(
or_(
Product.title.ilike(f"%{search}%"),
Product.description.ilike(f"%{search}%")
)
)
if min_price:
query = query.filter(Product.price >= min_price)
if max_price:
query = query.filter(Product.price <= max_price)
products = query.order_by(
Product.is_featured.desc(),
Product.created_at.desc()
).offset(skip).limit(limit).all()
return products
@router.get("/{product_id}", response_model=ProductResponse)
async def get_public_product(
vendor_id: int,
product_id: int,
db: Session = Depends(get_db)
):
"""Get product details (no auth required)"""
product = db.query(Product).filter(
Product.id == product_id,
Product.vendor_id == vendor_id,
Product.is_active == True
).first()
if not product:
raise HTTPException(status_code=404, detail="Product not found")
return product
@router.get("/search")
async def search_products(
vendor_id: int,
q: str,
db: Session = Depends(get_db)
):
"""Search products"""
# Implement search logic
pass
```
#### Customer Auth Endpoints (`app/api/v1/public/vendors/auth.py`)
```python
@router.post("/register", response_model=CustomerResponse)
async def register_customer(
vendor_id: int,
customer_data: CustomerRegister,
db: Session = Depends(get_db)
):
"""Register new customer"""
service = CustomerService()
customer = await service.register_customer(vendor_id, customer_data, db)
return customer
@router.post("/login")
async def login_customer(
vendor_id: int,
credentials: CustomerLogin,
db: Session = Depends(get_db)
):
"""Customer login"""
service = CustomerService()
customer, token = await service.authenticate_customer(
vendor_id, credentials.email, credentials.password, db
)
return {
"access_token": token,
"token_type": "bearer",
"customer": CustomerResponse.from_orm(customer)
}
```
#### Cart Endpoints (`app/api/v1/public/vendors/cart.py`)
```python
@router.get("/{session_id}", response_model=CartResponse)
async def get_cart(
vendor_id: int,
session_id: str,
current_customer: Optional[Customer] = Depends(get_current_customer_optional),
db: Session = Depends(get_db)
):
"""Get cart (session or customer)"""
service = CartService()
cart = await service.get_or_create_cart(
vendor_id,
db,
customer_id=current_customer.id if current_customer else None,
session_id=session_id if not current_customer else None
)
return cart
@router.post("/{session_id}/items", response_model=CartItemResponse)
async def add_to_cart(
vendor_id: int,
session_id: str,
item_data: CartItemAdd,
current_customer: Optional[Customer] = Depends(get_current_customer_optional),
db: Session = Depends(get_db)
):
"""Add item to cart"""
service = CartService()
cart = await service.get_or_create_cart(vendor_id, db, current_customer.id if current_customer else None, session_id)
item = await service.add_to_cart(cart, item_data.product_id, item_data.quantity, db)
return item
```
## 🎨 Frontend Implementation
### Templates
#### Shop Homepage (`templates/shop/home.html`)
```html
{% extends "shop/base_shop.html" %}
{% block content %}
<div x-data="shopHome()" x-init="loadFeaturedProducts()">
<!-- Hero Section -->
<div class="hero-section">
<h1>Welcome to {{ vendor.name }}</h1>
<p>{{ vendor.description }}</p>
<a href="/shop/products" class="btn btn-primary btn-lg">
Shop Now
</a>
</div>
<!-- Featured Products -->
<div class="products-section">
<h2>Featured Products</h2>
<div class="product-grid">
<template x-for="product in featuredProducts" :key="product.id">
<div class="product-card">
<a :href="`/shop/products/${product.id}`">
<img :src="product.featured_image || '/static/images/no-image.png'"
:alt="product.title">
<h3 x-text="product.title"></h3>
<p class="price"><span x-text="product.price.toFixed(2)"></span></p>
</a>
<button @click="addToCart(product.id)" class="btn btn-primary btn-sm">
Add to Cart
</button>
</div>
</template>
</div>
</div>
</div>
{% endblock %}
```
#### Product Detail (`templates/shop/product.html`)
```html
{% extends "shop/base_shop.html" %}
{% block content %}
<div x-data="productDetail()" x-init="loadProduct()">
<div class="product-detail">
<!-- Product Images -->
<div class="product-images">
<img :src="product.featured_image" :alt="product.title" class="main-image">
</div>
<!-- Product Info -->
<div class="product-info">
<h1 x-text="product.title"></h1>
<div class="price-section">
<span class="price"><span x-text="product.price"></span></span>
<template x-if="product.compare_at_price">
<span class="compare-price"><span x-text="product.compare_at_price"></span></span>
</template>
</div>
<div class="product-description" x-html="product.description"></div>
<!-- Quantity Selector -->
<div class="quantity-selector">
<label>Quantity</label>
<input
type="number"
x-model.number="quantity"
:min="1"
:max="product.stock_quantity"
>
<span class="stock-info" x-text="`${product.stock_quantity} in stock`"></span>
</div>
<!-- Add to Cart -->
<button
@click="addToCart()"
class="btn btn-primary btn-lg"
:disabled="!canAddToCart || adding"
>
<span x-show="!adding">Add to Cart</span>
<span x-show="adding" class="loading-spinner"></span>
</button>
</div>
</div>
</div>
<script>
window.productId = {{ product.id }};
window.vendorId = {{ vendor.id }};
</script>
{% endblock %}
{% block extra_scripts %}
<script>
function productDetail() {
return {
product: {},
quantity: 1,
adding: false,
loading: false,
get canAddToCart() {
return this.product.stock_quantity >= this.quantity && this.quantity > 0;
},
async loadProduct() {
this.loading = true;
try {
this.product = await apiClient.get(
`/api/v1/public/vendors/${window.vendorId}/products/${window.productId}`
);
} catch (error) {
showNotification('Failed to load product', 'error');
} finally {
this.loading = false;
}
},
async addToCart() {
this.adding = true;
try {
const sessionId = getOrCreateSessionId();
await apiClient.post(
`/api/v1/public/vendors/${window.vendorId}/cart/${sessionId}/items`,
{
product_id: this.product.id,
quantity: this.quantity
}
);
showNotification('Added to cart!', 'success');
updateCartCount(); // Update cart icon
} catch (error) {
showNotification(error.message || 'Failed to add to cart', 'error');
} finally {
this.adding = false;
}
}
}
}
</script>
{% endblock %}
```
#### Shopping Cart (`templates/shop/cart.html`)
Full Alpine.js reactive cart with real-time totals and quantity updates.
## ✅ Testing Checklist
### Backend Tests
- [ ] Customer registration works
- [ ] Duplicate email prevention works
- [ ] Customer login/authentication works
- [ ] Customer number generation is unique
- [ ] Public product browsing works without auth
- [ ] Product search/filtering works
- [ ] Cart creation works (session and customer)
- [ ] Add to cart works
- [ ] Update cart quantity works
- [ ] Remove from cart works
- [ ] Cart persists across sessions
- [ ] Cart merges after login
- [ ] Vendor isolation maintained
### Frontend Tests
- [ ] Shop homepage loads
- [ ] Product listing displays
- [ ] Product search works
- [ ] Product detail page works
- [ ] Customer registration form works
- [ ] Customer login works
- [ ] Add to cart works
- [ ] Cart updates in real-time (Alpine.js)
- [ ] Cart icon shows count
- [ ] Mobile responsive
## ➡️ Next Steps
After completing Slice 4, move to **Slice 5: Order Processing** to complete the checkout flow and order management.
---
**Slice 4 Status**: 📋 Not Started
**Dependencies**: Slices 1, 2, & 3 must be complete
**Estimated Duration**: 5 days

File diff suppressed because it is too large Load Diff

View File

@@ -1,306 +0,0 @@
# Multi-Tenant Ecommerce Platform - Vertical Slices Overview
## 📋 Development Approach
This project follows a **vertical slice development approach**, delivering complete, working user workflows incrementally. Each slice is fully functional and provides immediate value.
## 🎯 Technology Stack
### Backend
- **Framework**: FastAPI (Python 3.11+)
- **Database**: PostgreSQL with SQLAlchemy ORM
- **Authentication**: JWT tokens with bcrypt
- **Background Jobs**: Celery (for async tasks)
- **API Documentation**: Auto-generated OpenAPI/Swagger
### Frontend
- **Templating**: Jinja2 (server-side rendering)
- **JavaScript Framework**: Alpine.js v3.x (15KB, CDN-based)
- **Styling**: Custom CSS with CSS variables
- **AJAX**: Vanilla JavaScript with Fetch API
- **No Build Step**: Everything runs directly in the browser
### Why Alpine.js + Jinja2?
-**Lightweight**: Only 15KB, no build step required
-**Perfect Jinja2 Integration**: Works seamlessly with server-side templates
-**Reactive State**: Modern UX without framework complexity
-**Scoped Components**: Natural vendor isolation
-**Progressive Enhancement**: Works even if JS fails
-**Minimal Learning Curve**: Feels like inline JavaScript
## 📚 Slice Documentation Structure
Each slice has its own comprehensive markdown file:
### Slice 1: Multi-Tenant Foundation ✅ IN PROGRESS
**File**: `01_slice1_admin_vendor_foundation.md`
- Admin creates vendors through admin interface
- Vendor owner login with context detection
- Complete vendor data isolation
- **Status**: Backend mostly complete, frontend in progress
### Slice 2: Marketplace Integration
**File**: `02_slice2_marketplace_import.md`
- CSV import from Letzshop marketplace
- Background job processing
- Product staging area
- Import status tracking with Alpine.js
### Slice 3: Product Catalog Management
**File**: `03_slice3_product_catalog.md`
- Browse imported products in staging
- Select and publish to vendor catalog
- Product customization (pricing, descriptions)
- Inventory management
### Slice 4: Customer Shopping Experience
**File**: `04_slice4_customer_shopping.md`
- Public product browsing
- Customer registration/login
- Shopping cart with Alpine.js reactivity
- Product search functionality
### Slice 5: Order Processing
**File**: `05_slice5_order_processing.md`
- Checkout workflow
- Order placement
- Order management (vendor side)
- Order history (customer side)
## 🎯 Slice Completion Criteria
Each slice must pass these gates before moving to the next:
### Technical Criteria
- [ ] All backend endpoints implemented and tested
- [ ] Frontend pages created with Jinja2 templates
- [ ] Alpine.js components working (where applicable)
- [ ] Database migrations applied successfully
- [ ] Service layer business logic complete
- [ ] Exception handling implemented
- [ ] API documentation updated
### Quality Criteria
- [ ] Manual testing complete (all user flows)
- [ ] Security validation (vendor isolation)
- [ ] Performance acceptable (basic load testing)
- [ ] No console errors in browser
- [ ] Responsive design works on mobile
- [ ] Code follows project conventions
### Documentation Criteria
- [ ] Slice markdown file updated
- [ ] API endpoints documented
- [ ] Frontend components documented
- [ ] Database changes documented
- [ ] Testing checklist completed
## 🗓️ Estimated Timeline
### Week 1: Slice 1 - Foundation ⏳ Current
- Days 1-3: Backend completion (vendor context, admin APIs)
- Days 4-5: Frontend completion (admin pages, vendor login)
- **Deliverable**: Admin can create vendors, vendor owners can log in
### Week 2: Slice 2 - Import
- Days 1-3: Import backend (CSV processing, job tracking)
- Days 4-5: Import frontend (upload UI, status tracking)
- **Deliverable**: Vendors can import products from Letzshop
### Week 3: Slice 3 - Catalog
- Days 1-3: Catalog backend (product publishing, inventory)
- Days 4-5: Catalog frontend (product management UI)
- **Deliverable**: Vendors can manage product catalog
### Week 4: Slice 4 - Shopping
- Days 1-3: Customer backend (registration, cart, products)
- Days 4-5: Shop frontend (product browsing, cart)
- **Deliverable**: Customers can browse and add to cart
### Week 5: Slice 5 - Orders
- Days 1-3: Order backend (checkout, order management)
- Days 4-5: Order frontend (checkout flow, order history)
- **Deliverable**: Complete order workflow functional
## 📊 Progress Tracking
### ✅ Completed
- Database schema design
- Core models (User, Vendor, Roles)
- Authentication system
- Admin service layer
- Vendor context detection middleware
### 🔄 In Progress (Slice 1)
- Admin frontend pages (login, dashboard, vendors)
- Vendor frontend pages (login, dashboard)
- Admin API endpoints refinement
- Frontend-backend integration
### 📋 Upcoming (Slice 2)
- MarketplaceProduct model
- ImportJob model
- CSV processing service
- Import frontend with Alpine.js
## 🎨 Frontend Architecture Pattern
### Page Structure (Jinja2 + Alpine.js)
```html
{% extends "base.html" %}
{% block content %}
<div x-data="componentName()">
<!-- Alpine.js reactive component -->
<h1 x-text="title"></h1>
<!-- Jinja2 for initial data -->
<script>
window.initialData = {{ data|tojson }};
</script>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/admin/component.js"></script>
{% endblock %}
```
### Alpine.js Component Pattern
```javascript
function componentName() {
return {
// State
data: window.initialData || [],
loading: false,
error: null,
// Lifecycle
init() {
this.loadData();
},
// Methods
async loadData() {
this.loading = true;
try {
const response = await apiClient.get('/api/endpoint');
this.data = response;
} catch (error) {
this.error = error.message;
} finally {
this.loading = false;
}
}
}
}
```
## 🔑 Key Principles
### 1. Complete Features
Each slice delivers a complete, working feature from database to UI.
### 2. Vendor Isolation
All slices maintain strict vendor data isolation and context detection.
### 3. Progressive Enhancement
- HTML works without JavaScript
- Alpine.js enhances interactivity
- Jinja2 provides server-side rendering
### 4. API-First Design
- Backend exposes RESTful APIs
- Frontend consumes APIs via Fetch
- Clear separation of concerns
### 5. Clean Architecture
- Service layer for business logic
- Repository pattern for data access
- Exception-first error handling
- Dependency injection
## 📖 Documentation Files
### Slice Files (This Directory)
- `00_slices_overview.md` - This file
- `01_slice1_admin_vendor_foundation.md`
- `02_slice2_marketplace_import.md`
- `03_slice3_product_catalog.md`
- `04_slice4_customer_shopping.md`
- `05_slice5_order_processing.md`
### Supporting Documentation
- `../quick_start_guide.md` - Get running in 15 minutes
- `../css_structure_guide.txt` - CSS organization
- `../css_quick_reference.txt` - CSS usage guide
- `../12.project_readme_final.md` - Complete project README
## 🚀 Getting Started
### For Current Development (Slice 1)
1. Read `01_slice1_admin_vendor_foundation.md`
2. Follow setup in `../quick_start_guide.md`
3. Complete Slice 1 testing checklist
4. Move to Slice 2
### For New Features
1. Review this overview
2. Read the relevant slice documentation
3. Follow the implementation pattern
4. Test thoroughly before moving forward
## 💡 Tips for Success
### Working with Slices
- ✅ Complete one slice fully before starting the next
- ✅ Test each slice thoroughly
- ✅ Update documentation as you go
- ✅ Commit code after each slice completion
- ✅ Demo each slice to stakeholders
### Alpine.js Best Practices
- Keep components small and focused
- Use `x-data` for component state
- Use `x-init` for initialization
- Prefer `x-show` over `x-if` for toggles
- Use Alpine directives, not vanilla JS DOM manipulation
### Jinja2 Best Practices
- Extend base templates
- Use template inheritance
- Pass initial data from backend
- Keep logic in backend, not templates
- Use filters for formatting
## 🎯 Success Metrics
### By End of Slice 1
- Admin can create vendors ✅
- Vendor owners can log in ⏳
- Vendor context detection works ✅
- Complete data isolation verified
### By End of Slice 2
- Vendors can import CSV files
- Import jobs tracked in background
- Product staging area functional
### By End of Slice 3
- Products published to catalog
- Inventory management working
- Product customization enabled
### By End of Slice 4
- Customers can browse products
- Shopping cart functional
- Customer accounts working
### By End of Slice 5
- Complete checkout workflow
- Order management operational
- Platform ready for production
---
**Next Steps**: Start with `01_slice1_admin_vendor_foundation.md` to continue your current work on Slice 1.

File diff suppressed because it is too large Load Diff

View File

@@ -263,7 +263,7 @@ ENVIRONMENT=production
--- ---
**Full Documentation:** See `AUTHENTICATION_SYSTEM_DOCS.md` **Full Documentation:** See [Authentication System Documentation](authentication.md)
**Questions?** Contact backend team **Questions?** Contact backend team
--- ---

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,244 @@
# Error Handling
Comprehensive error handling system for the FastAPI multi-tenant e-commerce platform.
## Overview
The application uses a structured exception hierarchy with custom exception classes and centralized error handlers. All exceptions are logged, formatted consistently, and return appropriate HTTP status codes.
## Exception Hierarchy
### Base Exceptions
All custom exceptions inherit from base exception classes defined in `app.exceptions`:
```python
from app.exceptions import (
InvalidTokenException,
TokenExpiredException,
InvalidCredentialsException,
UserNotActiveException,
AdminRequiredException,
InsufficientPermissionsException,
RateLimitException
)
```
### Authentication Exceptions
| Exception | Status Code | Description |
|-----------|-------------|-------------|
| `InvalidTokenException` | 401 | JWT token is invalid, malformed, or missing required claims |
| `TokenExpiredException` | 401 | JWT token has expired |
| `InvalidCredentialsException` | 401 | Username/password authentication failed |
| `UserNotActiveException` | 403 | User account is inactive or disabled |
### Authorization Exceptions
| Exception | Status Code | Description |
|-----------|-------------|-------------|
| `AdminRequiredException` | 403 | Endpoint requires admin role |
| `InsufficientPermissionsException` | 403 | User lacks required permissions |
### Rate Limiting Exceptions
| Exception | Status Code | Description |
|-----------|-------------|-------------|
| `RateLimitException` | 429 | Too many requests, rate limit exceeded |
## Error Response Format
All errors return a consistent JSON format:
```json
{
"detail": "Error message describing what went wrong",
"status_code": 401,
"timestamp": "2024-11-16T13:00:00Z",
"path": "/api/v1/auth/login"
}
```
### Rate Limit Error Response
Rate limit errors include additional information:
```json
{
"detail": "Rate limit exceeded",
"status_code": 429,
"retry_after": 3600,
"timestamp": "2024-11-16T13:00:00Z",
"path": "/api/v1/resource"
}
```
## Usage Examples
### Raising Exceptions in Code
```python
from app.exceptions import InvalidCredentialsException, AdminRequiredException
# Authentication failure
if not user:
raise InvalidCredentialsException("User not found")
# Authorization check
if user.role != "admin":
raise AdminRequiredException()
```
### Catching Exceptions in Routes
```python
from fastapi import HTTPException
from app.exceptions import InvalidTokenException
@app.post("/api/v1/auth/protected")
async def protected_endpoint(token: str):
try:
user_data = auth_manager.verify_token(token)
return {"user": user_data}
except InvalidTokenException as e:
# Exception will be caught by global handler
raise
except Exception as e:
# Unexpected errors
logger.error(f"Unexpected error: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
```
## Context-Aware Error Handling
The error handling system is context-aware and provides different error formats based on the request context:
### API Requests (`/api/*`)
Returns JSON error responses suitable for API clients.
### Admin/Vendor Dashboard (`/admin/*`, `/vendor/*`)
Returns JSON errors or redirects to error pages based on accept headers.
### Shop Requests (`/shop/*`)
Returns themed error pages matching the vendor's shop design.
## Logging
All errors are automatically logged with the following information:
- Error type and message
- Request path and method
- User information (if authenticated)
- Stack trace (for unexpected errors)
- Timestamp
Example log output:
```
2024-11-16 13:00:00 [ERROR] middleware.auth: Token verification error: Token missing user identifier
2024-11-16 13:00:00 [ERROR] app.main: Request failed: POST /api/v1/auth/login - 401
```
## Best Practices
### 1. Use Specific Exceptions
Always use the most specific exception class available:
```python
# Good
raise InvalidCredentialsException("Invalid email or password")
# Avoid
raise HTTPException(status_code=401, detail="Invalid credentials")
```
### 2. Provide Meaningful Messages
Include context in error messages:
```python
# Good
raise InvalidTokenException("Token missing user identifier")
# Avoid
raise InvalidTokenException("Invalid token")
```
### 3. Don't Expose Sensitive Information
Never include sensitive data in error messages:
```python
# Good
raise InvalidCredentialsException("Invalid email or password")
# Avoid - reveals which field is wrong
raise InvalidCredentialsException(f"User {email} not found")
```
### 4. Log Before Raising
Log errors before raising them for debugging:
```python
try:
result = risky_operation()
except OperationFailed as e:
logger.error(f"Operation failed: {e}", exc_info=True)
raise InternalServerException("Operation failed")
```
## Testing Error Handling
### Unit Tests
```python
import pytest
from app.exceptions import InvalidTokenException
def test_invalid_token():
auth_manager = AuthManager()
with pytest.raises(InvalidTokenException) as exc_info:
auth_manager.verify_token("invalid-token")
assert "Could not validate credentials" in str(exc_info.value.message)
```
### Integration Tests
```python
def test_authentication_error_response(client):
response = client.post(
"/api/v1/auth/login",
json={"username": "wrong", "password": "wrong"}
)
assert response.status_code == 401
assert "detail" in response.json()
```
## Global Exception Handlers
The application registers global exception handlers in `main.py`:
```python
from fastapi import FastAPI
from app.exceptions import InvalidTokenException, RateLimitException
app = FastAPI()
@app.exception_handler(InvalidTokenException)
async def invalid_token_handler(request, exc):
return JSONResponse(
status_code=401,
content={
"detail": exc.message,
"status_code": 401,
"timestamp": datetime.now(timezone.utc).isoformat(),
"path": str(request.url.path)
}
)
```
## Related Documentation
- [Authentication](authentication.md) - Authentication-related exceptions
- [RBAC](RBAC.md) - Authorization and permission exceptions
- [Rate Limiting](rate-limiting.md) - Rate limit error handling
- [Testing Guide](../testing/testing-guide.md) - Testing error scenarios

View File

@@ -0,0 +1,401 @@
# Rate Limiting
API rate limiting implementation using sliding window algorithm for request throttling and abuse prevention.
## Overview
The platform uses an in-memory rate limiter with a sliding window algorithm to protect endpoints from abuse and ensure fair resource usage across all clients.
## Features
- **Sliding Window Algorithm**: Accurate rate limiting based on request timestamps
- **Per-Client Tracking**: Individual limits for each client
- **Automatic Cleanup**: Removes old entries to prevent memory leaks
- **Configurable Limits**: Set custom limits per endpoint
- **Decorator-Based**: Easy integration with FastAPI routes
## How It Works
### Sliding Window Algorithm
The rate limiter uses a sliding window approach:
1. Records timestamp of each request for a client
2. When new request comes in, removes expired timestamps
3. Counts remaining requests in the current window
4. Allows or denies based on the limit
```mermaid
graph LR
A[New Request] --> B{Check Window}
B --> C[Remove Old Timestamps]
C --> D{Count < Limit?}
D -->|Yes| E[Allow Request]
D -->|No| F[Reject - 429]
E --> G[Record Timestamp]
```
### Example Timeline
For a limit of 10 requests per 60 seconds:
```
Time: 0s 10s 20s 30s 40s 50s 60s 70s
|-----|-----|-----|-----|-----|-----|-----|
Req: 1 2 3 4 5 6 7 8 9 10 <-- Window (60s) --> 11
^
ALLOWED
(req 1-3 expired)
```
## Configuration
### Global Rate Limiter
A global rate limiter instance is available:
```python
from middleware.rate_limiter import RateLimiter
rate_limiter = RateLimiter()
```
### Rate Limiter Options
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `cleanup_interval` | int | 3600 | Seconds between automatic cleanups |
## Usage
### Using the Decorator
The easiest way to add rate limiting to an endpoint:
```python
from middleware.decorators import rate_limit
@app.post("/api/v1/resource")
@rate_limit(max_requests=10, window_seconds=60)
async def create_resource():
return {"status": "created"}
```
### Decorator Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `max_requests` | int | 100 | Maximum requests allowed |
| `window_seconds` | int | 3600 | Time window in seconds (1 hour) |
### Manual Usage
For more control, use the rate limiter directly:
```python
from middleware.rate_limiter import RateLimiter
from app.exceptions import RateLimitException
rate_limiter = RateLimiter()
@app.post("/api/v1/custom")
async def custom_endpoint(request: Request):
client_id = request.client.host
if not rate_limiter.allow_request(client_id, max_requests=5, window_seconds=60):
raise RateLimitException(
message="Too many requests",
retry_after=60
)
return {"status": "success"}
```
## Client Identification
### Current Implementation
By default, the rate limiter uses a simple client ID:
```python
client_id = "anonymous" # Basic implementation
```
### Production Recommendations
For production, implement proper client identification:
#### Option 1: By IP Address
```python
client_id = request.client.host
```
#### Option 2: By API Key
```python
api_key = request.headers.get("X-API-Key", "anonymous")
client_id = f"apikey:{api_key}"
```
#### Option 3: By Authenticated User
```python
if hasattr(request.state, 'user'):
client_id = f"user:{request.state.user.id}"
else:
client_id = f"ip:{request.client.host}"
```
#### Option 4: Combined Approach
```python
def get_client_id(request: Request) -> str:
# Prefer authenticated user
if hasattr(request.state, 'user'):
return f"user:{request.state.user.id}"
# Fall back to API key
api_key = request.headers.get("X-API-Key")
if api_key:
return f"key:{api_key}"
# Last resort: IP address
return f"ip:{request.client.host}"
```
## Common Rate Limit Configurations
### Conservative Limits
For expensive operations or authenticated endpoints:
```python
@rate_limit(max_requests=10, window_seconds=3600) # 10 per hour
async def expensive_operation():
pass
```
### Moderate Limits
For standard API operations:
```python
@rate_limit(max_requests=100, window_seconds=3600) # 100 per hour
async def standard_operation():
pass
```
### Generous Limits
For read-heavy operations:
```python
@rate_limit(max_requests=1000, window_seconds=3600) # 1000 per hour
async def read_operation():
pass
```
### Per-Minute Limits
For real-time operations:
```python
@rate_limit(max_requests=60, window_seconds=60) # 60 per minute
async def realtime_operation():
pass
```
## Error Response
When rate limit is exceeded, clients receive a 429 status code:
```json
{
"detail": "Rate limit exceeded",
"status_code": 429,
"retry_after": 3600,
"timestamp": "2024-11-16T13:00:00Z",
"path": "/api/v1/resource"
}
```
### Response Headers
Consider adding rate limit headers (future enhancement):
```http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1700145600
```
## Memory Management
### Automatic Cleanup
The rate limiter automatically cleans up old entries:
```python
# Runs every hour by default
cleanup_interval = 3600 # seconds
```
### Manual Cleanup
Force cleanup if needed:
```python
rate_limiter.cleanup_old_entries()
```
### Memory Considerations
For high-traffic applications:
- Each client uses approximately 8 bytes per request timestamp
- Example: 1000 clients x 100 requests = approximately 800 KB
- Consider Redis for distributed rate limiting
## Advanced Patterns
### Different Limits by Role
```python
from fastapi import Depends
from models.database.user import User
def get_rate_limit_for_user(user: User) -> tuple[int, int]:
limits = {
"admin": (10000, 3600), # 10k per hour
"vendor": (1000, 3600), # 1k per hour
"customer": (100, 3600), # 100 per hour
}
return limits.get(user.role, (100, 3600))
@app.post("/api/v1/resource")
async def resource_endpoint(
current_user: User = Depends(get_current_user)
):
max_req, window = get_rate_limit_for_user(current_user)
client_id = f"user:{current_user.id}"
if not rate_limiter.allow_request(client_id, max_req, window):
raise RateLimitException(retry_after=window)
return {"status": "success"}
```
### Endpoint-Specific Limits
```python
RATE_LIMITS = {
"/api/v1/auth/login": (5, 300), # 5 per 5 minutes
"/api/v1/products": (100, 3600), # 100 per hour
"/api/v1/orders": (50, 3600), # 50 per hour
}
@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
if request.url.path in RATE_LIMITS:
max_req, window = RATE_LIMITS[request.url.path]
client_id = request.client.host
if not rate_limiter.allow_request(client_id, max_req, window):
raise RateLimitException(retry_after=window)
return await call_next(request)
```
## Testing Rate Limits
### Unit Tests
```python
import pytest
from middleware.rate_limiter import RateLimiter
def test_rate_limiter_allows_requests_within_limit():
limiter = RateLimiter()
client = "test_client"
# Should allow first 5 requests
for i in range(5):
assert limiter.allow_request(client, max_requests=5, window_seconds=60)
# Should deny 6th request
assert not limiter.allow_request(client, max_requests=5, window_seconds=60)
```
### Integration Tests
```python
def test_rate_limit_endpoint(client):
# Make requests up to limit
for i in range(10):
response = client.post("/api/v1/resource")
assert response.status_code == 200
# Next request should be rate limited
response = client.post("/api/v1/resource")
assert response.status_code == 429
assert "retry_after" in response.json()
```
## Production Considerations
### Distributed Rate Limiting
For multi-server deployments, use Redis:
```python
import redis
from datetime import datetime, timezone
class RedisRateLimiter:
def __init__(self):
self.redis = redis.Redis(host='localhost', port=6379, db=0)
def allow_request(self, client_id: str, max_requests: int, window: int) -> bool:
key = f"ratelimit:{client_id}"
now = datetime.now(timezone.utc).timestamp()
# Remove old entries
self.redis.zremrangebyscore(key, 0, now - window)
# Count requests in window
count = self.redis.zcard(key)
if count < max_requests:
# Add new request
self.redis.zadd(key, {now: now})
self.redis.expire(key, window)
return True
return False
```
### Monitoring
Log rate limit violations for monitoring:
```python
@app.middleware("http")
async def rate_limit_monitoring(request: Request, call_next):
try:
response = await call_next(request)
return response
except RateLimitException as e:
logger.warning(
f"Rate limit exceeded",
extra={
"client": request.client.host,
"path": request.url.path,
"user_agent": request.headers.get("user-agent")
}
)
raise
```
## API Reference
For detailed implementation, see the RateLimiter class in `middleware/rate_limiter.py`.
## Related Documentation
- [Error Handling](error-handling.md) - HTTP error responses
- [Authentication](authentication.md) - API authentication
- [Error Handling](error-handling.md) - RateLimitException details
- [Authentication](authentication.md) - User-based rate limiting

View File

@@ -0,0 +1,522 @@
# Authentication & Role-Based Access Control (RBAC)
Complete guide to the authentication and authorization system powering the multi-tenant platform.
## Overview
The platform uses a JWT-based authentication system combined with role-based access control (RBAC) to secure all interfaces:
- **Admin** interface
- **Vendor** dashboard
- **Shop** storefront
- **REST API** endpoints
## Authentication System
### Technology Stack
- **JWT (JSON Web Tokens)**: Stateless authentication
- **bcrypt**: Secure password hashing
- **Jose**: JWT encoding/decoding library
- **FastAPI Security**: OAuth2 password bearer flow
### Authentication Flow
```mermaid
sequenceDiagram
participant Client
participant API
participant AuthManager
participant Database
Client->>API: POST /api/v1/auth/login<br/>{username, password}
API->>AuthManager: authenticate_user()
AuthManager->>Database: Query user by username/email
Database-->>AuthManager: User record
AuthManager->>AuthManager: verify_password()
AuthManager-->>API: User object
API->>AuthManager: create_access_token()
AuthManager-->>API: JWT token
API-->>Client: {access_token, token_type, expires_in}
Note over Client: Store token
Client->>API: GET /api/v1/resource<br/>Authorization: Bearer <token>
API->>AuthManager: verify_token()
AuthManager->>AuthManager: Decode JWT
AuthManager->>Database: Query user by ID
Database-->>AuthManager: User object
AuthManager-->>API: Current user
API->>API: Process request
API-->>Client: Resource data
```
## User Roles
The platform has three distinct user roles, each with specific permissions and access levels:
### Customer Role
**Access**: Public shop and own account space
**Capabilities**:
- Browse vendor shops
- Place orders
- Manage their own account and order history
- View order status
- Update profile information
- Can register directly from shop frontend
**Account Creation**: Self-registration via shop frontend (email verification required)
**Authentication**: Standard JWT authentication
### Vendor Role
**Access**: Vendor area based on permissions
**Types**:
- **Vendor Owner**: Full access to vendor dashboard and settings
- **Vendor Team Members**: Access based on assigned permissions
**Capabilities**:
- Manage products and inventory
- Process orders
- View analytics and reports
- Configure shop settings (owners only)
- Manage team members (owners only)
- Access vendor-specific APIs
**Account Creation**:
- Owners: Created automatically when admin creates a vendor
- Team members: Invited by vendor owner via email
**Permissions System**: Team members can have granular permissions for different areas
### Admin Role
**Access**: Full platform administration
**Capabilities**:
- Manage all vendors
- Create/manage vendor accounts
- Access system settings
- View all data across the platform
- Manage users of all types
- Access audit logs
- Platform-wide analytics
**Account Creation**: Created by super admins on the backend
**Super Privileges**: Admins can access all areas including vendor and customer sections
## Application Areas & Access Control
The platform has three distinct areas with different access requirements:
| Area | URL Pattern | Access | Purpose |
|------|-------------|--------|---------|
| **Admin** | `/admin/*` or `admin.platform.com` | Admin users only | Platform administration and vendor management |
| **Vendor** | `/vendor/*` | Vendor owners and team members | Vendor dashboard and shop management |
| **Shop** | `/shop/*`, custom domains, subdomains | Customers and public | Public-facing eCommerce storefront |
| **API** | `/api/*` | All authenticated users (role-based) | REST API for all operations |
## Account Registration Flow
### Admin Accounts
- ❌ Cannot register from frontend
- ✅ Created by super admins on the backend
- Used for: Platform administration
### Vendor Accounts
- ❌ Cannot register from frontend
-**Vendor Owners**: Automatically created when admin creates a new vendor
-**Team Members**: Invited by vendor owner via email invitation
- Activation: Upon clicking email verification link
### Customer Accounts
- ✅ Can register directly on vendor shop
- Activation: Upon clicking registration email link
- Used for: Shopping and order management
## Role Enforcement Methods
The `AuthManager` class provides several methods for role-based access control:
### require_admin()
Restricts access to admin users only.
**Usage**:
```python
from fastapi import Depends
from models.database.user import User
from middleware.auth import auth_manager
@app.get("/admin/dashboard")
async def admin_dashboard(
current_user: User = Depends(auth_manager.require_admin)
):
return {"message": "Admin access"}
```
**Raises**: `AdminRequiredException` if user is not admin
### require_vendor()
Allows access to vendor users and admins.
**Usage**:
```python
@app.get("/vendor/products")
async def vendor_products(
current_user: User = Depends(auth_manager.require_vendor)
):
return {"products": [...]}
```
**Raises**: `InsufficientPermissionsException` if user is not vendor or admin
### require_customer()
Allows access to customer users and admins.
**Usage**:
```python
@app.get("/shop/orders")
async def customer_orders(
current_user: User = Depends(auth_manager.require_customer)
):
return {"orders": [...]}
```
**Raises**: `InsufficientPermissionsException` if user is not customer or admin
### require_role()
Custom role enforcement for specific roles.
**Usage**:
```python
@app.get("/custom-endpoint")
@auth_manager.require_role("custom_role")
async def custom_endpoint(current_user: User):
return {"message": "Custom role access"}
```
**Returns**: Decorator function that validates role
## JWT Token Structure
### Token Payload
```json
{
"sub": "123", // User ID (JWT standard claim)
"username": "testuser", // Username for display
"email": "user@example.com", // User email
"role": "vendor", // User role
"exp": 1700000000, // Expiration timestamp (JWT standard)
"iat": 1699999000 // Issued at timestamp (JWT standard)
}
```
### Token Configuration
| Variable | Description | Default |
|----------|-------------|---------|
| `JWT_SECRET_KEY` | Secret key for JWT signing | Development key (change in production!) |
| `JWT_EXPIRE_MINUTES` | Token expiration time in minutes | 30 |
**Environment Configuration**:
```bash
# .env
JWT_SECRET_KEY=your-super-secret-key-change-in-production
JWT_EXPIRE_MINUTES=30
```
## Permission Hierarchy
```mermaid
graph TD
A[Admin] --> B[Full Platform Access]
A --> C[Can Access All Areas]
D[Vendor Owner] --> E[Vendor Dashboard]
D --> F[Team Management]
D --> G[Shop Settings]
D --> H[All Vendor Data]
I[Vendor Team Member] --> E
I --> J[Limited Based on Permissions]
K[Customer] --> L[Shop Access]
K --> M[Own Orders]
K --> N[Own Profile]
```
**Admin Override**: Admin users have implicit access to all areas, including vendor and customer sections. This allows admins to provide support and manage the platform effectively.
## Security Features
### Password Security
**Hashing**:
- Algorithm: bcrypt
- Automatic salt generation
- Configurable work factor
**Example**:
```python
from middleware.auth import auth_manager
# Hash password
hashed = auth_manager.hash_password("user_password")
# Verify password
is_valid = auth_manager.verify_password("user_password", hashed)
```
### Token Security
**Features**:
- Signed with secret key (prevents tampering)
- Includes expiration time
- Stateless (no server-side session storage)
- Short-lived (30 minutes default)
**Best Practices**:
- Use HTTPS in production
- Store tokens securely on client
- Implement token refresh mechanism
- Clear tokens on logout
### Protection Against Common Attacks
**SQL Injection**:
- ✅ SQLAlchemy ORM with parameterized queries
- ✅ Input validation with Pydantic
**XSS (Cross-Site Scripting)**:
- ✅ Jinja2 auto-escaping
- ✅ Content Security Policy headers
**CSRF (Cross-Site Request Forgery)**:
- ✅ JWT tokens in Authorization header (not cookies)
- ✅ SameSite cookie attribute for session cookies
**Brute Force**:
- ✅ Rate limiting on auth endpoints
- ✅ Account lockout after failed attempts (future)
## Authentication Endpoints
### Register User
```http
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"username": "testuser",
"password": "securepassword123"
}
```
**Response**:
```json
{
"id": 123,
"username": "testuser",
"email": "user@example.com",
"role": "customer",
"is_active": true
}
```
### Login
```http
POST /api/v1/auth/login
Content-Type: application/json
{
"username": "testuser",
"password": "securepassword123"
}
```
**Response**:
```json
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "bearer",
"expires_in": 1800
}
```
### Using Authentication
Include the JWT token in the Authorization header:
```http
GET /api/v1/resource
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```
## Error Handling
### Authentication Errors
| Error | Status Code | Description |
|-------|-------------|-------------|
| `InvalidCredentialsException` | 401 | Username/password incorrect |
| `InvalidTokenException` | 401 | JWT token invalid or malformed |
| `TokenExpiredException` | 401 | JWT token has expired |
| `UserNotActiveException` | 403 | User account is inactive |
### Authorization Errors
| Error | Status Code | Description |
|-------|-------------|-------------|
| `AdminRequiredException` | 403 | Endpoint requires admin role |
| `InsufficientPermissionsException` | 403 | User lacks required permissions |
## Testing Authentication
### Unit Tests
```python
import pytest
from middleware.auth import AuthManager
def test_password_hashing():
auth_manager = AuthManager()
password = "test_password"
hashed = auth_manager.hash_password(password)
assert auth_manager.verify_password(password, hashed)
assert not auth_manager.verify_password("wrong_password", hashed)
def test_create_token():
auth_manager = AuthManager()
user = create_test_user(role="vendor")
token_data = auth_manager.create_access_token(user)
assert "access_token" in token_data
assert "token_type" in token_data
assert token_data["token_type"] == "bearer"
```
### Integration Tests
```python
def test_login_flow(client):
# Register user
response = client.post("/api/v1/auth/register", json={
"username": "testuser",
"email": "test@example.com",
"password": "password123"
})
assert response.status_code == 200
# Login
response = client.post("/api/v1/auth/login", json={
"username": "testuser",
"password": "password123"
})
assert response.status_code == 200
token = response.json()["access_token"]
# Access protected endpoint
response = client.get(
"/api/v1/profile",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200
```
## Best Practices
### For Developers
1. **Always hash passwords**: Never store plain text passwords
2. **Use dependency injection**: Leverage FastAPI's `Depends` for auth
3. **Validate tokens**: Always validate tokens on protected endpoints
4. **Check permissions**: Verify user has required role/permissions
5. **Log auth events**: Track login, logout, failed attempts
### For Operations
1. **Strong secret keys**: Use long, random JWT secret keys
2. **HTTPS only**: Never send tokens over HTTP
3. **Token expiration**: Keep token lifetimes short
4. **Rotate secrets**: Periodically rotate JWT secret keys
5. **Monitor auth logs**: Watch for suspicious activity
### For Security
1. **Rate limiting**: Limit auth endpoint requests
2. **Account lockout**: Implement after N failed attempts
3. **Email verification**: Require email confirmation
4. **Password policies**: Enforce strong password requirements
5. **2FA support**: Consider adding two-factor authentication
## Examples
### Protecting an Endpoint
```python
from fastapi import Depends, APIRouter
from sqlalchemy.orm import Session
from middleware.auth import auth_manager
from app.core.database import get_db
from models.database.user import User
router = APIRouter()
@router.get("/vendors")
async def get_vendors(
current_user: User = Depends(auth_manager.require_admin),
db: Session = Depends(get_db)
):
"""Only admins can list all vendors."""
vendors = db.query(Vendor).all()
return {"vendors": vendors}
```
### Multi-Role Access
```python
@router.get("/dashboard")
async def dashboard(
current_user: User = Depends(auth_manager.get_current_user),
db: Session = Depends(get_db)
):
"""Accessible by all authenticated users, but returns different data."""
if current_user.role == "admin":
# Admin sees everything
data = get_admin_dashboard(db)
elif current_user.role == "vendor":
# Vendor sees their data only
data = get_vendor_dashboard(db, current_user.id)
else:
# Customer sees their orders
data = get_customer_dashboard(db, current_user.id)
return data
```
## Related Documentation
- [Middleware Stack](middleware.md) - System-wide request processing
- [Error Handling](../api/error-handling.md) - Exception handling
- [Backend API Reference](../backend/middleware-reference.md) - Technical AuthManager docs
- [Testing Guide](../testing/testing-guide.md) - Testing authentication
## Technical Reference
For detailed API documentation of authentication classes and methods:
- [AuthManager API Reference](../backend/middleware-reference.md#authentication-authorization)

View File

@@ -0,0 +1,448 @@
# Middleware Stack
The middleware stack is the backbone of the multi-tenant system, handling tenant detection, context injection, and theme loading for all requests.
## Overview
The application uses a custom middleware stack that processes **every request** regardless of whether it's:
- REST API calls (`/api/*`)
- Admin interface pages (`/admin/*`)
- Vendor dashboard pages (`/vendor/*`)
- Shop pages (`/shop/*` or custom domains)
This middleware layer is **system-wide** and enables the multi-tenant architecture to function seamlessly.
## Middleware Components
### 1. Logging Middleware
**Purpose**: Request/response logging and performance monitoring
**What it does**:
- Logs every incoming request with method, path, and client IP
- Measures request processing time
- Logs response status codes
- Adds `X-Process-Time` header with processing duration
- Logs errors with stack traces
**Example Log Output**:
```
INFO Request: GET /admin/dashboard from 192.168.1.100
INFO Response: 200 for GET /admin/dashboard (0.143s)
```
**Configuration**: Runs first to capture full request timing
### 2. Vendor Context Middleware
**Purpose**: Detect which vendor's shop the request is for (multi-tenant core)
**What it does**:
- Detects vendor from:
- Custom domain (e.g., `customdomain.com`)
- Subdomain (e.g., `vendor1.platform.com`)
- Path prefix (e.g., `/vendor/vendor1/` or `/vendors/vendor1/`)
- Queries database to find vendor by domain or code
- Injects vendor object into `request.state.vendor`
- Extracts "clean path" (path without vendor prefix)
- Sets `request.state.clean_path` for routing
**Example**:
```
Request: https://wizamart.platform.com/shop/products
Middleware detects: vendor_code = "wizamart"
Queries database: SELECT * FROM vendors WHERE code = 'wizamart'
Injects: request.state.vendor = <Vendor object>
request.state.vendor_id = 1
request.state.clean_path = "/shop/products"
```
**Why it's critical**: Without this, the system wouldn't know which vendor's data to show
**See**: [Multi-Tenant System](multi-tenant.md) for routing modes
### 3. Path Rewrite Middleware
**Purpose**: Rewrite request paths for proper FastAPI routing
**What it does**:
- Uses the `clean_path` extracted by VendorContextMiddleware
- Rewrites `request.scope['path']` to remove vendor prefix
- Allows FastAPI routes to match correctly
**Example**:
```
Original path: /vendor/WIZAMART/shop/products
Clean path: /shop/products (set by VendorContextMiddleware)
Path Rewrite Middleware changes request path to: /shop/products
FastAPI router can now match: @app.get("/shop/products")
```
**Why it's needed**: FastAPI routes don't include vendor prefix, so we strip it
### 4. Context Detection Middleware
**Purpose**: Determine the type/context of the request
**What it does**:
- Analyzes the request path (using clean_path)
- Determines which interface is being accessed:
- `API` - `/api/*` paths
- `ADMIN` - `/admin/*` paths or `admin.*` subdomain
- `VENDOR_DASHBOARD` - `/vendor/*` paths (management area)
- `SHOP` - Storefront pages (has vendor + not admin/vendor/API)
- `FALLBACK` - Unknown context
- Injects `request.state.context_type`
**Detection Rules**:
```python
if path.startswith("/api/"):
context = API
elif path.startswith("/admin/") or host.startswith("admin."):
context = ADMIN
elif path.startswith("/vendor/"):
context = VENDOR_DASHBOARD
elif request.state.vendor exists:
context = SHOP
else:
context = FALLBACK
```
**Why it's useful**: Error handlers and templates adapt based on context
### 5. Theme Context Middleware
**Purpose**: Load vendor-specific theme settings
**What it does**:
- Checks if request has a vendor (from VendorContextMiddleware)
- Queries database for vendor's theme settings
- Injects theme configuration into `request.state.theme`
- Provides default theme if vendor has no custom theme
**Theme Data Structure**:
```python
{
"primary_color": "#3B82F6",
"secondary_color": "#10B981",
"logo_url": "/static/vendors/wizamart/logo.png",
"favicon_url": "/static/vendors/wizamart/favicon.ico",
"custom_css": "/* vendor-specific styles */"
}
```
**Why it's needed**: Each vendor shop can have custom branding
## Middleware Execution Order
### The Stack (First to Last)
```mermaid
graph TD
A[Client Request] --> B[1. LoggingMiddleware]
B --> C[2. VendorContextMiddleware]
C --> D[3. PathRewriteMiddleware]
D --> E[4. ContextDetectionMiddleware]
E --> F[5. ThemeContextMiddleware]
F --> G[6. FastAPI Router]
G --> H[Route Handler]
H --> I[Response]
I --> J[Client]
```
### Why This Order Matters
**Critical Dependencies**:
1. **LoggingMiddleware first**
- Needs to wrap everything to measure total time
- Must log errors from all other middleware
2. **VendorContextMiddleware second**
- Must run before PathRewriteMiddleware (provides clean_path)
- Must run before ContextDetectionMiddleware (provides vendor)
- Must run before ThemeContextMiddleware (provides vendor_id)
3. **PathRewriteMiddleware third**
- Depends on clean_path from VendorContextMiddleware
- Must run before ContextDetectionMiddleware (rewrites path)
4. **ContextDetectionMiddleware fourth**
- Uses clean_path from VendorContextMiddleware
- Uses rewritten path from PathRewriteMiddleware
- Provides context_type for ThemeContextMiddleware
5. **ThemeContextMiddleware last**
- Depends on vendor from VendorContextMiddleware
- Depends on context_type from ContextDetectionMiddleware
**Breaking this order will break the application!**
## Request State Variables
Middleware components inject these variables into `request.state`:
| Variable | Set By | Type | Used By | Description |
|----------|--------|------|---------|-------------|
| `vendor` | VendorContextMiddleware | Vendor | Theme, Templates | Current vendor object |
| `vendor_id` | VendorContextMiddleware | int | Queries, Theme | Current vendor ID |
| `clean_path` | VendorContextMiddleware | str | PathRewrite, Context | Path without vendor prefix |
| `context_type` | ContextDetectionMiddleware | RequestContext | Theme, Error handlers | Request context enum |
| `theme` | ThemeContextMiddleware | dict | Templates | Vendor theme config |
### Using in Route Handlers
```python
from fastapi import Request
@app.get("/shop/products")
async def get_products(request: Request):
# Access vendor
vendor = request.state.vendor
vendor_id = request.state.vendor_id
# Access context
context = request.state.context_type
# Access theme
theme = request.state.theme
# Use in queries
products = db.query(Product).filter(
Product.vendor_id == vendor_id
).all()
return {"vendor": vendor.name, "products": products}
```
### Using in Templates
```jinja2
{# Access vendor #}
<h1>{{ request.state.vendor.name }}</h1>
{# Access theme #}
<style>
:root {
--primary-color: {{ request.state.theme.primary_color }};
--secondary-color: {{ request.state.theme.secondary_color }};
}
</style>
{# Access context #}
{% if request.state.context_type.value == "admin" %}
<div class="admin-badge">Admin Mode</div>
{% endif %}
```
## Request Flow Example
### Example: Shop Product Page Request
**URL**: `https://wizamart.myplatform.com/shop/products`
**Middleware Processing**:
```
1. LoggingMiddleware
↓ Starts timer
↓ Logs: "Request: GET /shop/products from 192.168.1.100"
2. VendorContextMiddleware
↓ Detects subdomain: "wizamart"
↓ Queries DB: vendor = get_vendor_by_code("wizamart")
↓ Sets: request.state.vendor = <Vendor: Wizamart>
↓ Sets: request.state.vendor_id = 1
↓ Sets: request.state.clean_path = "/shop/products"
3. PathRewriteMiddleware
↓ Path already clean (no rewrite needed for subdomain mode)
↓ request.scope['path'] = "/shop/products"
4. ContextDetectionMiddleware
↓ Analyzes path: "/shop/products"
↓ Has vendor: Yes
↓ Not admin/api/vendor dashboard
↓ Sets: request.state.context_type = RequestContext.SHOP
5. ThemeContextMiddleware
↓ Loads theme for vendor_id = 1
↓ Sets: request.state.theme = {...theme config...}
6. FastAPI Router
↓ Matches route: @app.get("/shop/products")
↓ Calls handler function
7. Route Handler
↓ Accesses: request.state.vendor_id
↓ Queries: products WHERE vendor_id = 1
↓ Renders template with vendor data
8. Response
↓ Returns HTML with vendor theme
9. LoggingMiddleware (response phase)
↓ Logs: "Response: 200 for GET /shop/products (0.143s)"
↓ Adds header: X-Process-Time: 0.143
```
## Error Handling in Middleware
Each middleware component handles errors gracefully:
### VendorContextMiddleware
- If vendor not found: Sets `request.state.vendor = None`
- If database error: Logs error, allows request to continue
- Fallback: Request proceeds without vendor context
### ContextDetectionMiddleware
- If clean_path missing: Uses original path
- If vendor missing: Defaults to FALLBACK context
- Always sets a context_type (never None)
### ThemeContextMiddleware
- If vendor missing: Skips theme loading
- If theme query fails: Uses default theme
- If no theme exists: Returns empty theme dict
**Design Philosophy**: Middleware should never crash the application. Degrade gracefully.
## Performance Considerations
### Database Queries
**Per Request**:
- 1 query in VendorContextMiddleware (vendor lookup) - cached by DB
- 1 query in ThemeContextMiddleware (theme lookup) - cached by DB
**Total**: ~2 DB queries per request
**Optimization Opportunities**:
- Implement Redis caching for vendor lookups
- Cache theme data in memory
- Use connection pooling (already enabled)
### Memory Usage
Minimal per-request overhead:
- Small objects stored in `request.state`
- No global state maintained
- Garbage collected after response
### Latency
Typical overhead: **< 5ms** per request
- Vendor lookup: ~2ms
- Theme lookup: ~2ms
- Context detection: <1ms
## Configuration
Middleware is registered in `main.py`:
```python
# Add in REVERSE order (LIFO execution)
app.add_middleware(LoggingMiddleware)
app.add_middleware(ThemeContextMiddleware)
app.add_middleware(ContextDetectionMiddleware)
app.add_middleware(VendorContextMiddleware)
```
**Note**: FastAPI's `add_middleware` executes in **reverse order** (Last In, First Out)
## Testing Middleware
### Unit Testing
Test each middleware component in isolation:
```python
from middleware.vendor_context import VendorContextManager
def test_vendor_detection_subdomain():
# Mock request
request = create_mock_request(host="wizamart.platform.com")
# Test detection
manager = VendorContextManager()
vendor = manager.detect_vendor_from_subdomain(request)
assert vendor.code == "wizamart"
```
### Integration Testing
Test the full middleware stack:
```python
def test_shop_request_flow(client):
response = client.get(
"/shop/products",
headers={"Host": "wizamart.platform.com"}
)
assert response.status_code == 200
assert "Wizamart" in response.text
```
**See**: [Testing Guide](../testing/testing-guide.md)
## Debugging Middleware
### Enable Debug Logging
```python
import logging
logging.getLogger("middleware").setLevel(logging.DEBUG)
```
### Check Request State
In route handlers:
```python
@app.get("/debug")
async def debug_state(request: Request):
return {
"vendor": request.state.vendor.name if hasattr(request.state, 'vendor') else None,
"vendor_id": getattr(request.state, 'vendor_id', None),
"clean_path": getattr(request.state, 'clean_path', None),
"context_type": request.state.context_type.value if hasattr(request.state, 'context_type') else None,
"theme": bool(getattr(request.state, 'theme', None))
}
```
### Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| Vendor not detected | Wrong host header | Check domain configuration |
| Context is FALLBACK | Path doesn't match patterns | Check route prefix |
| Theme not loading | Vendor ID missing | Check VendorContextMiddleware runs first |
| Sidebar broken | Variable name conflict | See frontend troubleshooting |
## Related Documentation
- [Multi-Tenant System](multi-tenant.md) - Detailed routing modes
- [Request Flow](request-flow.md) - Complete request journey
- [Authentication & RBAC](auth-rbac.md) - Security middleware
- [Backend API Reference](../backend/middleware-reference.md) - Technical API docs
- [Frontend Development](../frontend/overview.md) - Using middleware state in frontend
## Technical Reference
For detailed API documentation of middleware classes and methods, see:
- [Backend Middleware Reference](../backend/middleware-reference.md)
This includes:
- Complete class documentation
- Method signatures
- Parameter details
- Return types
- Auto-generated from source code

View File

@@ -0,0 +1,601 @@
# Multi-Tenant System
Complete guide to the multi-tenant architecture supporting custom domains, subdomains, and path-based routing.
## Overview
The Wizamart platform supports **three deployment modes** for multi-tenancy, allowing each vendor to have their own isolated shop while sharing the same application instance and database.
**Key Concept**: One application, multiple isolated vendor shops, each accessible via different URLs.
## The Three Routing Modes
### 1. Custom Domain Mode
**Concept**: Each vendor has their own domain pointing to the platform.
**Example**:
```
customdomain1.com → Vendor 1 Shop
anothershop.com → Vendor 2 Shop
beststore.net → Vendor 3 Shop
```
**How it works**:
1. Vendor registers a custom domain
2. Domain's DNS is configured to point to the platform
3. Platform detects vendor by matching domain in database
4. Vendor's shop is displayed with their theme/branding
**Use Case**: Professional vendors who want their own branded domain
**Configuration**:
```python
# Database: vendor_domains table
vendor_id | domain
----------|------------------
1 | customdomain1.com
2 | anothershop.com
3 | beststore.net
```
### 2. Subdomain Mode
**Concept**: Each vendor gets a subdomain of the platform domain.
**Example**:
```
vendor1.platform.com → Vendor 1 Shop
vendor2.platform.com → Vendor 2 Shop
vendor3.platform.com → Vendor 3 Shop
admin.platform.com → Admin Interface
```
**How it works**:
1. Vendor is assigned a unique code (e.g., "vendor1")
2. Subdomain is automatically available: `{code}.platform.com`
3. Platform detects vendor from subdomain prefix
4. No DNS configuration needed by vendor
**Use Case**: Easy setup, no custom domain required
**Configuration**:
```python
# Vendors table
id | code | name
---|---------|----------
1 | vendor1 | Vendor One Shop
2 | vendor2 | Vendor Two Shop
3 | vendor3 | Vendor Three Shop
```
### 3. Path-Based Mode
**Concept**: All vendors share the same domain, differentiated by URL path.
**Example**:
```
platform.com/vendor/vendor1/shop → Vendor 1 Shop
platform.com/vendor/vendor2/shop → Vendor 2 Shop
platform.com/vendors/vendor3/shop → Vendor 3 Shop (alternative)
```
**How it works**:
1. URL path includes vendor code
2. Platform extracts vendor code from path
3. Path is rewritten for routing
4. All vendors on same domain
**Use Case**: Simplest deployment, single domain certificate
**Path Patterns**:
- `/vendor/{code}/shop/*` - Storefront pages
- `/vendor/{code}/api/*` - API endpoints (if needed)
- `/vendors/{code}/shop/*` - Alternative pattern
## Routing Mode Comparison
| Feature | Custom Domain | Subdomain | Path-Based |
|---------|---------------|-----------|------------|
| **Professionalism** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| **Setup Complexity** | High (DNS required) | Low (automatic) | Very Low |
| **SSL Complexity** | Medium (wildcard or per-domain) | Low (wildcard SSL) | Very Low (single cert) |
| **SEO Benefits** | Best (own domain) | Good | Limited |
| **Cost** | High (domain + SSL) | Low (wildcard SSL) | Lowest |
| **Isolation** | Best (separate domain) | Good | Good |
| **URL Appearance** | `shop.com` | `shop.platform.com` | `platform.com/vendor/shop` |
## Implementation Details
### Vendor Detection Logic
The `VendorContextMiddleware` detects vendors using this priority:
```python
def detect_vendor(request):
host = request.headers.get("host")
# 1. Try custom domain first
vendor = find_by_custom_domain(host)
if vendor:
return vendor, "custom_domain"
# 2. Try subdomain
if host != settings.platform_domain:
vendor_code = host.split('.')[0]
vendor = find_by_code(vendor_code)
if vendor:
return vendor, "subdomain"
# 3. Try path-based
path = request.url.path
if path.startswith("/vendor/") or path.startswith("/vendors/"):
vendor_code = extract_code_from_path(path)
vendor = find_by_code(vendor_code)
if vendor:
return vendor, "path_based"
return None, None
```
### Path Extraction
For path-based routing, clean paths are extracted:
**Example 1**: Single vendor prefix
```
Original: /vendor/WIZAMART/shop/products
Extracted: vendor_code = "WIZAMART"
Clean: /shop/products
```
**Example 2**: Plural vendors prefix
```
Original: /vendors/WIZAMART/shop/products
Extracted: vendor_code = "WIZAMART"
Clean: /shop/products
```
**Why Clean Path?**
- FastAPI routes don't include vendor prefix
- Routes defined as: `@app.get("/shop/products")`
- Path must be rewritten to match routes
## Database Schema
### Vendors Table
```sql
CREATE TABLE vendors (
id SERIAL PRIMARY KEY,
code VARCHAR(50) UNIQUE NOT NULL, -- For subdomain/path routing
name VARCHAR(255) NOT NULL,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW()
);
```
### Vendor Domains Table
```sql
CREATE TABLE vendor_domains (
id SERIAL PRIMARY KEY,
vendor_id INTEGER REFERENCES vendors(id),
domain VARCHAR(255) UNIQUE NOT NULL, -- Custom domain
is_verified BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW()
);
```
**Example Data**:
```sql
-- Vendors
INSERT INTO vendors (code, name) VALUES
('wizamart', 'Wizamart Shop'),
('techstore', 'Tech Store'),
('fashionhub', 'Fashion Hub');
-- Custom Domains
INSERT INTO vendor_domains (vendor_id, domain) VALUES
(1, 'wizamart.com'),
(2, 'mytechstore.net');
```
## Deployment Scenarios
### Scenario 1: Small Platform (Path-Based)
**Setup**:
- Single domain: `myplatform.com`
- All vendors use path-based routing
- Single SSL certificate
- Simplest infrastructure
**URLs**:
```
myplatform.com/admin
myplatform.com/vendor/shop1/shop
myplatform.com/vendor/shop2/shop
myplatform.com/vendor/shop3/shop
```
**Infrastructure**:
```
[Internet] → [Single Server] → [PostgreSQL]
myplatform.com
```
### Scenario 2: Medium Platform (Subdomain)
**Setup**:
- Main domain: `myplatform.com`
- Vendors get subdomains automatically
- Wildcard SSL certificate (`*.myplatform.com`)
- Better branding for vendors
**URLs**:
```
admin.myplatform.com
shop1.myplatform.com
shop2.myplatform.com
shop3.myplatform.com
```
**Infrastructure**:
```
[Internet] → [Load Balancer] → [App Servers] → [PostgreSQL]
*.myplatform.com
```
### Scenario 3: Large Platform (Mixed Mode)
**Setup**:
- Supports all three modes
- Premium vendors get custom domains
- Regular vendors use subdomains
- Free tier uses path-based
**URLs**:
```
# Custom domains (premium)
customdomain.com → Vendor 1
anotherdomain.com → Vendor 2
# Subdomains (standard)
shop3.myplatform.com → Vendor 3
shop4.myplatform.com → Vendor 4
# Path-based (free tier)
myplatform.com/vendor/shop5/shop → Vendor 5
myplatform.com/vendor/shop6/shop → Vendor 6
```
**Infrastructure**:
```
[CDN/Load Balancer]
|
+-----------------+------------------+
| | |
[App Server 1] [App Server 2] [App Server 3]
| | |
+-----------------+------------------+
|
[PostgreSQL Cluster]
```
## DNS Configuration
### For Custom Domains
**Vendor Side**:
```
# DNS A Record
customdomain.com. A 203.0.113.10 (platform IP)
# Or CNAME
customdomain.com. CNAME myplatform.com.
```
**Platform Side**:
- Add domain to `vendor_domains` table
- Generate SSL certificate (Let's Encrypt)
- Verify domain ownership
### For Subdomains
**Platform Side**:
```
# Wildcard DNS
*.myplatform.com. A 203.0.113.10
# Or individual subdomains
shop1.myplatform.com. A 203.0.113.10
shop2.myplatform.com. A 203.0.113.10
```
**SSL Certificate**:
```bash
# Wildcard certificate
*.myplatform.com
myplatform.com
```
## Tenant Isolation
### Data Isolation
Every database query is scoped to `vendor_id`:
```python
# Example: Get products for current vendor
products = db.query(Product).filter(
Product.vendor_id == request.state.vendor_id
).all()
# Example: Create order for vendor
order = Order(
vendor_id=request.state.vendor_id,
customer_id=customer_id,
# ... other fields
)
```
**Critical**: ALWAYS filter by `vendor_id` in queries!
### Theme Isolation
Each vendor has independent theme settings:
```python
# Vendor 1 theme
{
"primary_color": "#3B82F6",
"logo_url": "/static/vendors/vendor1/logo.png"
}
# Vendor 2 theme
{
"primary_color": "#10B981",
"logo_url": "/static/vendors/vendor2/logo.png"
}
```
### File Storage Isolation
Vendor files stored in separate directories:
```
static/
└── vendors/
├── vendor1/
│ ├── logo.png
│ ├── favicon.ico
│ └── products/
│ ├── product1.jpg
│ └── product2.jpg
└── vendor2/
├── logo.png
└── products/
└── product1.jpg
```
## Request Examples
### Example 1: Custom Domain Request
**Request**:
```http
GET /shop/products HTTP/1.1
Host: customdomain.com
```
**Processing**:
```
1. VendorContextMiddleware
- Checks: domain = "customdomain.com"
- Queries: vendor_domains WHERE domain = "customdomain.com"
- Finds: vendor_id = 1
- Sets: request.state.vendor = <Vendor 1>
2. ContextDetectionMiddleware
- Analyzes: path = "/shop/products"
- Sets: context_type = SHOP
3. ThemeContextMiddleware
- Queries: vendor_themes WHERE vendor_id = 1
- Sets: request.state.theme = {...}
4. Route Handler
- Queries: products WHERE vendor_id = 1
- Renders: template with Vendor 1 theme
```
### Example 2: Subdomain Request
**Request**:
```http
GET /shop/products HTTP/1.1
Host: wizamart.myplatform.com
```
**Processing**:
```
1. VendorContextMiddleware
- Checks: host != "myplatform.com"
- Extracts: subdomain = "wizamart"
- Queries: vendors WHERE code = "wizamart"
- Sets: request.state.vendor = <Vendor "wizamart">
2-4. Same as Example 1
```
### Example 3: Path-Based Request
**Request**:
```http
GET /vendor/WIZAMART/shop/products HTTP/1.1
Host: myplatform.com
```
**Processing**:
```
1. VendorContextMiddleware
- Checks: path starts with "/vendor/"
- Extracts: code = "WIZAMART"
- Queries: vendors WHERE code = "WIZAMART"
- Sets: request.state.vendor = <Vendor>
- Sets: request.state.clean_path = "/shop/products"
2. PathRewriteMiddleware
- Rewrites: request.scope['path'] = "/shop/products"
3-4. Same as previous examples
```
## Testing Multi-Tenancy
### Unit Tests
```python
def test_vendor_detection_custom_domain():
request = MockRequest(host="customdomain.com")
middleware = VendorContextMiddleware()
vendor, mode = middleware.detect_vendor(request, db)
assert vendor.code == "vendor1"
assert mode == "custom_domain"
def test_vendor_detection_subdomain():
request = MockRequest(host="shop1.platform.com")
middleware = VendorContextMiddleware()
vendor, mode = middleware.detect_vendor(request, db)
assert vendor.code == "shop1"
assert mode == "subdomain"
```
### Integration Tests
```python
def test_shop_page_multi_tenant(client):
# Test subdomain routing
response = client.get(
"/shop/products",
headers={"Host": "wizamart.platform.com"}
)
assert "Wizamart" in response.text
# Test different vendor
response = client.get(
"/shop/products",
headers={"Host": "techstore.platform.com"}
)
assert "Tech Store" in response.text
```
## Security Considerations
### 1. Tenant Isolation
**Always scope queries**:
```python
# ✅ Good - Scoped to vendor
products = db.query(Product).filter(
Product.vendor_id == request.state.vendor_id
).all()
# ❌ Bad - Not scoped, leaks data across tenants!
products = db.query(Product).all()
```
### 2. Domain Verification
Before activating custom domain:
1. Verify DNS points to platform
2. Check domain ownership (email/file verification)
3. Generate SSL certificate
4. Mark domain as verified
### 3. Input Validation
Validate vendor codes:
```python
# Sanitize vendor code
vendor_code = vendor_code.lower().strip()
# Validate format
if not re.match(r'^[a-z0-9-]{3,50}$', vendor_code):
raise ValidationError("Invalid vendor code")
```
## Performance Optimization
### 1. Cache Vendor Lookups
```python
# Cache vendor by domain/code
@lru_cache(maxsize=1000)
def get_vendor_by_code(code: str):
return db.query(Vendor).filter(Vendor.code == code).first()
```
### 2. Database Indexes
```sql
-- Index for fast lookups
CREATE INDEX idx_vendors_code ON vendors(code);
CREATE INDEX idx_vendor_domains_domain ON vendor_domains(domain);
CREATE INDEX idx_products_vendor_id ON products(vendor_id);
```
### 3. Connection Pooling
Ensure database connection pool is properly configured:
```python
# sqlalchemy engine
engine = create_engine(
DATABASE_URL,
pool_size=20,
max_overflow=40,
pool_pre_ping=True
)
```
## Related Documentation
- [Middleware Stack](middleware.md) - How vendor detection works
- [Request Flow](request-flow.md) - Complete request journey
- [Architecture Overview](overview.md) - System architecture
- [Authentication & RBAC](auth-rbac.md) - Multi-tenant security
## Migration Guide
### Adding Multi-Tenancy to Existing Tables
```python
# Alembic migration
def upgrade():
# Add vendor_id to existing table
op.add_column('products',
sa.Column('vendor_id', sa.Integer(), nullable=True)
)
# Set default vendor for existing data
op.execute("UPDATE products SET vendor_id = 1 WHERE vendor_id IS NULL")
# Make non-nullable
op.alter_column('products', 'vendor_id', nullable=False)
# Add foreign key
op.create_foreign_key(
'fk_products_vendor',
'products', 'vendors',
['vendor_id'], ['id']
)
# Add index
op.create_index('idx_products_vendor_id', 'products', ['vendor_id'])
```

View File

@@ -0,0 +1,356 @@
# System Architecture
High-level overview of the Wizamart multi-tenant e-commerce platform architecture.
## Overview
Wizamart is a **multi-tenant e-commerce platform** that supports three distinct interfaces:
- **Admin** - Platform administration and vendor management
- **Vendor** - Vendor dashboard for managing shops
- **Shop** - Customer-facing storefronts
## Technology Stack
### Backend
- **Framework**: FastAPI (Python)
- **Database**: PostgreSQL with SQLAlchemy ORM
- **Authentication**: JWT-based with bcrypt password hashing
- **API**: RESTful JSON APIs
### Frontend
- **Templates**: Jinja2 server-side rendering
- **JavaScript**: Alpine.js for reactive components
- **CSS**: Tailwind CSS
- **Icons**: Lucide Icons
### Infrastructure
- **Web Server**: Uvicorn (ASGI)
- **Middleware**: Custom middleware stack for multi-tenancy
- **Static Files**: FastAPI StaticFiles
## System Components
### 1. Multi-Tenant Routing
The platform supports three deployment modes:
#### Custom Domain Mode
```
customdomain.com → Vendor 1 Shop
anotherdomain.com → Vendor 2 Shop
```
#### Subdomain Mode
```
vendor1.platform.com → Vendor 1 Shop
vendor2.platform.com → Vendor 2 Shop
admin.platform.com → Admin Interface
```
#### Path-Based Mode
```
platform.com/vendor/vendor1/shop → Vendor 1 Shop
platform.com/vendor/vendor2/shop → Vendor 2 Shop
platform.com/admin → Admin Interface
```
**See:** [Multi-Tenant System](multi-tenant.md) for detailed implementation
### 2. Middleware Stack
Custom middleware handles:
- Vendor detection and context injection
- Request context detection (API/Admin/Vendor/Shop)
- Theme loading for vendor shops
- Request/response logging
- Path rewriting for multi-tenant routing
**See:** [Middleware Stack](middleware.md) for complete documentation
### 3. Authentication & Authorization
- JWT-based authentication
- Role-based access control (RBAC)
- Three user roles: Admin, Vendor, Customer
- Hierarchical permissions system
**See:** [Authentication & RBAC](auth-rbac.md) for details
### 4. Request Flow
```mermaid
graph TB
A[Client Request] --> B[Logging Middleware]
B --> C[Vendor Context Middleware]
C --> D[Path Rewrite Middleware]
D --> E[Context Detection Middleware]
E --> F[Theme Context Middleware]
F --> G{Request Type?}
G -->|API /api/*| H[API Router]
G -->|Admin /admin/*| I[Admin Page Router]
G -->|Vendor /vendor/*| J[Vendor Page Router]
G -->|Shop /shop/*| K[Shop Page Router]
H --> L[JSON Response]
I --> M[Admin HTML]
J --> N[Vendor HTML]
K --> O[Shop HTML]
```
**See:** [Request Flow](request-flow.md) for detailed journey
## Application Areas
### Admin Interface (`/admin/*`)
**Purpose**: Platform administration
**Features**:
- Vendor management
- User management
- System settings
- Audit logs
- Analytics dashboard
**Access**: Admin users only
### Vendor Dashboard (`/vendor/*`)
**Purpose**: Vendor shop management
**Features**:
- Product management
- Inventory tracking
- Order processing
- Shop settings
- Team member management
- Analytics
**Access**: Vendor users (owners and team members)
### Shop Interface (`/shop/*` or custom domains)
**Purpose**: Customer-facing storefront
**Features**:
- Product browsing
- Shopping cart
- Checkout
- Order tracking
- Customer account
**Access**: Public + registered customers
### API (`/api/*`)
**Purpose**: RESTful JSON API for all operations
**Features**:
- All CRUD operations
- Authentication endpoints
- Data export/import
- Webhook support
**Access**: Authenticated users based on role
## Data Architecture
### Database Schema
```
┌─────────────────┐
│ vendors │ ← Multi-tenant root
└────────┬────────┘
├─── vendor_domains
├─── vendor_themes
├─── vendor_settings
├─── products ────┬─── product_variants
│ ├─── product_images
│ └─── product_categories
├─── orders ──────┬─── order_items
│ └─── order_status_history
└─── customers ───┬─── customer_addresses
└─── customer_sessions
```
### Key Design Patterns
1. **Tenant Isolation**: All data scoped to vendor_id
2. **Soft Deletes**: Records marked as deleted, not removed
3. **Audit Trail**: All changes tracked with user and timestamp
4. **JSON Fields**: Flexible metadata storage
## Security Architecture
### Authentication Flow
```
1. User submits credentials
2. Server validates against database
3. JWT token generated with user info
4. Token returned to client
5. Client includes token in subsequent requests
6. Server validates token on each request
```
### Authorization Layers
1. **Route-level**: Middleware checks user authentication
2. **Role-level**: Decorators enforce role requirements
3. **Resource-level**: Services check ownership/permissions
4. **Tenant-level**: All queries scoped to vendor
**See:** [Authentication & RBAC](auth-rbac.md)
## Scalability Considerations
### Current Architecture
- **Single server**: Suitable for small to medium deployments
- **In-memory rate limiting**: Per-process limits
- **Session state**: Stateless JWT tokens
### Future Enhancements
- **Horizontal scaling**: Load balancer + multiple app servers
- **Redis integration**: Distributed rate limiting and caching
- **Database replication**: Read replicas for scaling
- **CDN integration**: Static asset distribution
- **Message queue**: Async task processing (Celery + Redis)
## Development Workflow
### Local Development
```bash
# Setup
make install-all
make db-setup
# Development
make dev # Start FastAPI server
make docs-serve # Start documentation server
# Testing
make test # Run all tests
make test-coverage # Run with coverage report
# Code Quality
make format # Format code (black + isort)
make lint # Run linters (ruff + mypy)
```
### Project Structure
```
project/
├── app/ # Application code
│ ├── api/ # API routes
│ ├── routes/ # Page routes (HTML)
│ ├── services/ # Business logic
│ ├── core/ # Core functionality
│ └── exceptions/ # Custom exceptions
├── middleware/ # Custom middleware
│ ├── auth.py # Authentication
│ ├── vendor_context.py # Tenant detection
│ ├── context_middleware.py # Context detection
│ └── theme_context.py # Theme loading
├── models/ # Data models
│ ├── database/ # SQLAlchemy models
│ └── schema/ # Pydantic schemas
├── static/ # Static files
│ ├── admin/ # Admin assets
│ ├── vendor/ # Vendor assets
│ └── shop/ # Shop assets
├── templates/ # Jinja2 templates
│ ├── admin/
│ ├── vendor/
│ └── shop/
├── tests/ # Test suite
│ ├── unit/
│ └── integration/
└── docs/ # Documentation
├── architecture/ # System architecture
├── frontend/ # Frontend guides
├── backend/ # Backend development
└── api/ # API documentation
```
## Monitoring & Observability
### Logging
- Structured logging with Python logging module
- Request/response logging via middleware
- Error tracking with stack traces
- Audit logging for admin actions
### Performance Monitoring
- Request timing headers (`X-Process-Time`)
- Database query monitoring (SQLAlchemy echo)
- Slow query identification
- Memory usage tracking
## Deployment Architecture
### Production Deployment
```
┌─────────────┐
Internet ───────────│ Load Balancer│
└──────┬───────┘
┌──────────────┼──────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ App │ │ App │ │ App │
│ Server 1│ │ Server 2│ │ Server 3│
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘
┌──────▼───────┐
│ PostgreSQL │
│ (Primary + │
│ Replicas) │
└──────────────┘
```
**See:** [Deployment Documentation](../deployment/index.md)
## Related Documentation
- [Multi-Tenant System](multi-tenant.md) - Detailed multi-tenancy implementation
- [Middleware Stack](middleware.md) - Complete middleware documentation
- [Authentication & RBAC](auth-rbac.md) - Security and access control
- [Request Flow](request-flow.md) - Detailed request processing
- [Frontend Architecture](../frontend/overview.md) - Frontend development guides
- [Backend Development](../backend/overview.md) - Backend development guides
- [API Documentation](../api/index.md) - API reference
## Quick Links
### For Developers
- [Creating a New Admin Page](../frontend/admin/page-templates.md)
- [Backend Development Guide](../backend/overview.md)
- [Database Migrations](../development/database-migrations.md)
### For Operations
- [Deployment Guide](../deployment/production.md)
- [Environment Configuration](../deployment/environment.md)
- [Database Setup](../getting-started/database-setup.md)
### For Team Members
- [Contributing Guide](../development/contributing.md)
- [PyCharm Setup](../development/pycharm-configuration-make.md)
- [Troubleshooting](../development/troubleshooting.md)

View File

@@ -0,0 +1,564 @@
# Request Flow
Complete journey of a request through the Wizamart platform, from client to response.
## Overview
This document traces how requests flow through the multi-tenant system, showing the path through middleware, routing, and response generation.
## High-Level Flow
```mermaid
graph TB
A[Client Request] --> B{Request Type?}
B -->|API| C[REST API Flow]
B -->|HTML Page| D[Page Rendering Flow]
C --> E[Middleware Stack]
D --> E
E --> F[Vendor Detection]
F --> G[Context Detection]
G --> H[Theme Loading]
H --> I[Router]
I -->|API| J[API Handler]
I -->|Page| K[Route Handler]
J --> L[JSON Response]
K --> M[Jinja2 Template]
M --> N[HTML Response]
L --> O[Client]
N --> O
```
## Detailed Request Flow
### 1. Client Sends Request
**Example Requests**:
```http
# Shop page request (subdomain mode)
GET https://wizamart.platform.com/shop/products
Host: wizamart.platform.com
# API request
GET https://platform.com/api/v1/products?vendor_id=1
Authorization: Bearer eyJ0eXAi...
Host: platform.com
# Admin page request
GET https://platform.com/admin/vendors
Authorization: Bearer eyJ0eXAi...
Host: platform.com
```
### 2. LoggingMiddleware (Entry Point)
**What happens**:
- Request enters the application
- Timer starts
- Request logged with method, path, client IP
**Request State**:
```python
# Start time recorded
start_time = time.time()
# Log entry
logger.info(f"Request: GET /shop/products from 192.168.1.100")
```
**Output**: Nothing added to `request.state` yet
### 3. VendorContextMiddleware
**What happens**:
- Analyzes host header and path
- Determines routing mode (custom domain / subdomain / path-based)
- Queries database for vendor
- Extracts clean path
**Example Processing** (Subdomain Mode):
```python
# Input
host = "wizamart.platform.com"
path = "/shop/products"
# Detection logic
if host != settings.platform_domain:
# Subdomain detected
vendor_code = host.split('.')[0] # "wizamart"
# Query database
vendor = db.query(Vendor).filter(
Vendor.code == vendor_code
).first()
# Set request state
request.state.vendor = vendor
request.state.vendor_id = vendor.id
request.state.clean_path = "/shop/products" # Already clean
```
**Request State After**:
```python
request.state.vendor = <Vendor: Wizamart>
request.state.vendor_id = 1
request.state.clean_path = "/shop/products"
```
### 4. PathRewriteMiddleware
**What happens**:
- Checks if `clean_path` is different from original path
- If different, rewrites the request path for FastAPI routing
**Example** (Path-Based Mode):
```python
# Input (path-based mode)
original_path = "/vendor/WIZAMART/shop/products"
clean_path = "/shop/products" # From VendorContextMiddleware
# Path rewrite
if clean_path != original_path:
request.scope['path'] = clean_path
request._url = request.url.replace(path=clean_path)
```
**Request State After**: No changes to state, but internal path updated
### 5. ContextDetectionMiddleware
**What happens**:
- Analyzes the clean path
- Determines request context type
- Sets context in request state
**Detection Logic**:
```python
path = request.state.clean_path # "/shop/products"
if path.startswith("/api/"):
context = RequestContext.API
elif path.startswith("/admin/"):
context = RequestContext.ADMIN
elif path.startswith("/vendor/"):
context = RequestContext.VENDOR_DASHBOARD
elif hasattr(request.state, 'vendor') and request.state.vendor:
context = RequestContext.SHOP # ← Our example
else:
context = RequestContext.FALLBACK
request.state.context_type = context
```
**Request State After**:
```python
request.state.context_type = RequestContext.SHOP
```
### 6. ThemeContextMiddleware
**What happens**:
- Checks if request has a vendor
- Loads theme configuration from database
- Injects theme into request state
**Theme Loading**:
```python
if hasattr(request.state, 'vendor_id'):
theme = db.query(VendorTheme).filter(
VendorTheme.vendor_id == request.state.vendor_id
).first()
request.state.theme = {
"primary_color": theme.primary_color,
"secondary_color": theme.secondary_color,
"logo_url": theme.logo_url,
"custom_css": theme.custom_css
}
```
**Request State After**:
```python
request.state.theme = {
"primary_color": "#3B82F6",
"secondary_color": "#10B981",
"logo_url": "/static/vendors/wizamart/logo.png",
"custom_css": "..."
}
```
### 7. FastAPI Router
**What happens**:
- Request reaches FastAPI's router
- Router matches path to registered route
- Route dependencies are resolved
- Handler function is called
**Route Matching**:
```python
# Request path (after rewrite): "/shop/products"
# Matches this route
@app.get("/shop/products")
async def get_shop_products(request: Request):
# Handler code
pass
```
### 8. Route Handler Execution
**Example Handler**:
```python
from app.routes import shop_pages
@router.get("/shop/products")
async def shop_products_page(
request: Request,
db: Session = Depends(get_db)
):
# Access vendor from request state
vendor = request.state.vendor
vendor_id = request.state.vendor_id
# Query products for this vendor
products = db.query(Product).filter(
Product.vendor_id == vendor_id
).all()
# Render template with context
return templates.TemplateResponse(
"shop/products.html",
{
"request": request,
"vendor": vendor,
"products": products,
"theme": request.state.theme
}
)
```
### 9. Template Rendering (Jinja2)
**Template** (`templates/shop/products.html`):
```jinja2
<!DOCTYPE html>
<html>
<head>
<title>{{ vendor.name }} - Products</title>
<style>
:root {
--primary-color: {{ theme.primary_color }};
--secondary-color: {{ theme.secondary_color }};
}
</style>
</head>
<body>
<h1>{{ vendor.name }} Shop</h1>
<div class="products">
{% for product in products %}
<div class="product-card">
<h2>{{ product.name }}</h2>
<p>{{ product.price }}</p>
</div>
{% endfor %}
</div>
</body>
</html>
```
**Rendered HTML**:
```html
<!DOCTYPE html>
<html>
<head>
<title>Wizamart - Products</title>
<style>
:root {
--primary-color: #3B82F6;
--secondary-color: #10B981;
}
</style>
</head>
<body>
<h1>Wizamart Shop</h1>
<div class="products">
<div class="product-card">
<h2>Product 1</h2>
<p>$29.99</p>
</div>
<!-- More products... -->
</div>
</body>
</html>
```
### 10. Response Sent Back
**LoggingMiddleware (Response Phase)**:
- Calculates total request time
- Logs response status and duration
- Adds performance header
**Logging**:
```python
duration = time.time() - start_time # 0.143 seconds
logger.info(
f"Response: 200 for GET /shop/products (0.143s)"
)
# Add header
response.headers["X-Process-Time"] = "0.143"
```
**Final Response**:
```http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Process-Time: 0.143
Content-Length: 2847
```
## Flow Diagrams by Request Type
### API Request Flow
```mermaid
sequenceDiagram
participant Client
participant Logging
participant Vendor
participant Context
participant Router
participant Handler
participant DB
Client->>Logging: GET /api/v1/products?vendor_id=1
Logging->>Vendor: Pass request
Note over Vendor: No vendor detection<br/>(API uses query param)
Vendor->>Context: Pass request
Context->>Context: Detect API context
Note over Context: context_type = API
Context->>Router: Route request
Router->>Handler: Call API handler
Handler->>DB: Query products
DB-->>Handler: Product data
Handler-->>Router: JSON response
Router-->>Client: {products: [...]}
```
### Admin Page Flow
```mermaid
sequenceDiagram
participant Client
participant Logging
participant Vendor
participant Context
participant Theme
participant Router
participant Handler
participant Template
Client->>Logging: GET /admin/vendors
Logging->>Vendor: Pass request
Note over Vendor: No vendor<br/>(Admin area)
Vendor->>Context: Pass request
Context->>Context: Detect Admin context
Note over Context: context_type = ADMIN
Context->>Theme: Pass request
Note over Theme: Skip theme<br/>(No vendor)
Theme->>Router: Route request
Router->>Handler: Call handler
Handler->>Template: Render admin template
Template-->>Client: Admin HTML page
```
### Shop Page Flow (Full Multi-Tenant)
```mermaid
sequenceDiagram
participant Client
participant Logging
participant Vendor
participant Path
participant Context
participant Theme
participant Router
participant Handler
participant DB
participant Template
Client->>Logging: GET /shop/products<br/>Host: wizamart.platform.com
Logging->>Vendor: Pass request
Vendor->>DB: Query vendor by subdomain
DB-->>Vendor: Vendor object
Note over Vendor: Set vendor, vendor_id, clean_path
Vendor->>Path: Pass request
Note over Path: Path already clean
Path->>Context: Pass request
Context->>Context: Detect Shop context
Note over Context: context_type = SHOP
Context->>Theme: Pass request
Theme->>DB: Query theme
DB-->>Theme: Theme config
Note over Theme: Set theme in request.state
Theme->>Router: Route request
Router->>Handler: Call handler
Handler->>DB: Query products for vendor
DB-->>Handler: Product list
Handler->>Template: Render with theme
Template-->>Client: Themed shop HTML
```
## Request State Timeline
Showing how `request.state` is built up through the middleware stack:
```
Initial State: {}
After VendorContextMiddleware:
{
vendor: <Vendor: Wizamart>,
vendor_id: 1,
clean_path: "/shop/products"
}
After ContextDetectionMiddleware:
{
vendor: <Vendor: Wizamart>,
vendor_id: 1,
clean_path: "/shop/products",
context_type: RequestContext.SHOP
}
After ThemeContextMiddleware:
{
vendor: <Vendor: Wizamart>,
vendor_id: 1,
clean_path: "/shop/products",
context_type: RequestContext.SHOP,
theme: {
primary_color: "#3B82F6",
secondary_color: "#10B981",
logo_url: "/static/vendors/wizamart/logo.png",
custom_css: "..."
}
}
```
## Performance Metrics
Typical request timings:
| Component | Time | Percentage |
|-----------|------|------------|
| Middleware Stack | 5ms | 3% |
| - VendorContextMiddleware | 2ms | 1% |
| - ContextDetectionMiddleware | <1ms | <1% |
| - ThemeContextMiddleware | 2ms | 1% |
| Database Queries | 15ms | 10% |
| Business Logic | 50ms | 35% |
| Template Rendering | 75ms | 52% |
| **Total** | **145ms** | **100%** |
## Error Handling in Flow
### Middleware Errors
If middleware encounters an error:
```python
try:
# Middleware logic
vendor = detect_vendor(request)
except Exception as e:
logger.error(f"Vendor detection failed: {e}")
# Set default/None
request.state.vendor = None
# Continue to next middleware
```
### Handler Errors
If route handler raises an exception:
```python
try:
response = await handler(request)
except HTTPException as e:
# FastAPI handles HTTP exceptions
return error_response(e.status_code, e.detail)
except Exception as e:
# Custom exception handler
logger.error(f"Handler error: {e}")
return error_response(500, "Internal Server Error")
```
## Related Documentation
- [Middleware Stack](middleware.md) - Detailed middleware documentation
- [Multi-Tenant System](multi-tenant.md) - Tenant routing modes
- [Authentication & RBAC](auth-rbac.md) - Security flow
- [Architecture Overview](overview.md) - System architecture
## Debugging Request Flow
### Enable Request Logging
```python
import logging
logging.getLogger("middleware").setLevel(logging.DEBUG)
logging.getLogger("fastapi").setLevel(logging.DEBUG)
```
### Add Debug Endpoint
```python
@app.get("/debug/request-state")
async def debug_state(request: Request):
return {
"path": request.url.path,
"host": request.headers.get("host"),
"vendor": request.state.vendor.name if hasattr(request.state, 'vendor') else None,
"vendor_id": getattr(request.state, 'vendor_id', None),
"clean_path": getattr(request.state, 'clean_path', None),
"context_type": request.state.context_type.value if hasattr(request.state, 'context_type') else None,
"has_theme": bool(getattr(request.state, 'theme', None))
}
```
### Check Middleware Order
In `main.py`, middleware registration order is critical:
```python
# REVERSE order (Last In, First Out)
app.add_middleware(LoggingMiddleware) # Runs first
app.add_middleware(ThemeContextMiddleware) # Runs fifth
app.add_middleware(ContextDetectionMiddleware) # Runs fourth
app.add_middleware(VendorContextMiddleware) # Runs second
```
app.add_middleware(ThemeContextMiddleware) # Runs fifth
app.add_middleware(ContextDetectionMiddleware) # Runs fourth
app.add_middleware(VendorContextMiddleware) # Runs second
```

View File

@@ -0,0 +1,305 @@
# API Reference
Complete technical reference for all middleware components, utilities, and core classes.
## Overview
This reference provides detailed API documentation for all internal modules and classes. All documentation is auto-generated from source code docstrings.
---
## Authentication & Authorization
### AuthManager
The core authentication manager handling JWT tokens, password hashing, and role-based access control.
::: middleware.auth.AuthManager
options:
show_source: false
heading_level: 4
show_root_heading: false
show_root_toc_entry: false
members:
- __init__
- hash_password
- verify_password
- authenticate_user
- create_access_token
- verify_token
- get_current_user
- require_role
- require_admin
- require_vendor
- require_customer
- create_default_admin_user
---
## Multi-Tenant Context Management
### VendorContextManager
Detects and manages vendor context from custom domains, subdomains, or path-based routing. This is the foundation of the multi-tenant system.
**Key Features:**
- Custom domain routing (customdomain.com → Vendor)
- Subdomain routing (vendor1.platform.com → Vendor)
- Path-based routing (/vendor/vendor1/ → Vendor)
- Clean path extraction for nested routing
::: middleware.vendor_context.VendorContextManager
options:
show_source: false
heading_level: 4
show_root_heading: false
### VendorContextMiddleware
ASGI middleware that wraps VendorContextManager for FastAPI integration.
::: middleware.vendor_context.VendorContextMiddleware
options:
show_source: false
heading_level: 4
show_root_heading: false
---
## Request Context Detection
### RequestContext
Enum defining all possible request context types in the application.
::: middleware.context_middleware.RequestContext
options:
show_source: false
heading_level: 4
show_root_heading: false
members:
- API
- ADMIN
- VENDOR_DASHBOARD
- SHOP
- FALLBACK
### ContextManager
Detects the type of request (API, Admin, Vendor Dashboard, Shop) based on URL patterns.
**Context Detection Rules:**
- `/api/` → API context
- `/admin/` → Admin context
- `/vendor/` → Vendor Dashboard context
- `/shop/` → Shop context
- Default → Fallback context
::: middleware.context_middleware.ContextManager
options:
show_source: false
heading_level: 4
show_root_heading: false
### ContextMiddleware
ASGI middleware for context detection. Must run AFTER VendorContextMiddleware.
::: middleware.context_middleware.ContextMiddleware
options:
show_source: false
heading_level: 4
show_root_heading: false
---
## Theme Management
### ThemeContextManager
Manages vendor-specific theme configuration and injection into request context.
::: middleware.theme_context.ThemeContextManager
options:
show_source: false
heading_level: 4
show_root_heading: false
### ThemeContextMiddleware
ASGI middleware for theme injection. Must run AFTER ContextDetectionMiddleware.
::: middleware.theme_context.ThemeContextMiddleware
options:
show_source: false
heading_level: 4
show_root_heading: false
---
## Rate Limiting
### RateLimiter
In-memory rate limiter using a sliding window algorithm for request throttling.
**Features:**
- Sliding window algorithm for accurate rate limiting
- Per-client tracking
- Automatic cleanup of old entries
- Configurable limits and time windows
::: middleware.rate_limiter.RateLimiter
options:
show_source: false
heading_level: 4
show_root_heading: false
### Rate Limiting Decorator
Decorator for applying rate limits to FastAPI endpoints.
::: middleware.decorators.rate_limit
options:
show_source: true
heading_level: 4
show_root_heading: false
**Usage Example:**
```python
from middleware.decorators import rate_limit
@app.post("/api/v1/resource")
@rate_limit(max_requests=10, window_seconds=60)
async def create_resource():
return {"status": "created"}
```
---
## Logging & Monitoring
### LoggingMiddleware
Middleware for request/response logging and performance monitoring.
**Logged Information:**
- Request method, path, and client IP
- Response status code
- Request processing time
- Errors and exceptions
**Added Headers:**
- `X-Process-Time`: Request processing duration in seconds
::: middleware.logging_middleware.LoggingMiddleware
options:
show_source: false
heading_level: 4
show_root_heading: false
---
## Path Rewriting
### path_rewrite_middleware
Middleware function that rewrites request paths for path-based vendor routing.
**Purpose:**
Allows `/vendor/VENDORCODE/shop/products` to be internally routed as `/shop/products` for proper FastAPI route matching.
**Execution Order:**
Must run AFTER VendorContextMiddleware and BEFORE ContextDetectionMiddleware.
::: middleware.path_rewrite_middleware.path_rewrite_middleware
options:
show_source: true
heading_level: 4
show_root_heading: false
---
## Middleware Execution Order
The middleware stack must be configured in the correct order for proper functionality:
```mermaid
graph TD
A[Request] --> B[LoggingMiddleware]
B --> C[VendorContextMiddleware]
C --> D[PathRewriteMiddleware]
D --> E[ContextMiddleware]
E --> F[ThemeContextMiddleware]
F --> G[Application Routes]
G --> H[Response]
```
**Critical Dependencies:**
1. **VendorContextMiddleware** must run first to detect vendor
2. **PathRewriteMiddleware** needs `clean_path` from VendorContext
3. **ContextMiddleware** needs rewritten path
4. **ThemeContextMiddleware** needs context type
---
## Request State Variables
Middleware components inject the following variables into `request.state`:
| Variable | Set By | Type | Description |
|----------|--------|------|-------------|
| `vendor` | VendorContextMiddleware | Vendor | Current vendor object |
| `vendor_id` | VendorContextMiddleware | int | Current vendor ID |
| `clean_path` | VendorContextMiddleware | str | Path without vendor prefix |
| `context_type` | ContextMiddleware | RequestContext | Request context (API/Admin/Vendor/Shop) |
| `theme` | ThemeContextMiddleware | dict | Vendor theme configuration |
**Usage in Routes:**
```python
from fastapi import Request
@app.get("/shop/products")
async def get_products(request: Request):
vendor = request.state.vendor
context = request.state.context_type
theme = request.state.theme
return {"vendor": vendor.name, "context": context}
```
---
## Best Practices
### Error Handling
All middleware should properly handle exceptions and log errors for debugging:
```python
try:
# Middleware logic
except Exception as e:
logger.error(f"Middleware error: {e}")
raise
```
### Performance
- Keep middleware logic minimal and fast
- Use async/await properly for non-blocking operations
- Log performance metrics for monitoring
### Testing
- Test middleware in isolation
- Mock request.state for unit tests
- Test middleware execution order
- Verify error handling paths
For testing examples, see the [Testing Guide](../testing/testing-guide.md).
---
## Related Documentation
- [Authentication Guide](../api/authentication.md) - User authentication and JWT tokens
- [RBAC Documentation](../api/RBAC.md) - Role-based access control
- [Error Handling](../api/error-handling.md) - Exception handling patterns
- [Rate Limiting](../api/rate-limiting.md) - API rate limiting strategies

484
docs/backend/overview.md Normal file
View File

@@ -0,0 +1,484 @@
# Backend Development
Guide for developing backend features, services, and API endpoints.
## Overview
The Wizamart backend is built with FastAPI and follows a service-oriented architecture pattern. This guide covers backend development practices, patterns, and technical references.
## Backend Structure
```
app/
├── api/ # API routes (REST endpoints)
│ ├── v1/ # Version 1 API
│ │ ├── admin/ # Admin API endpoints
│ │ ├── vendor/ # Vendor API endpoints
│ │ └── shop/ # Shop API endpoints
│ └── main.py # API router configuration
├── routes/ # Page routes (HTML)
│ ├── admin_pages.py # Admin page routes
│ ├── vendor_pages.py # Vendor page routes
│ └── shop_pages.py # Shop page routes
├── services/ # Business logic layer
│ ├── admin_service.py
│ ├── product_service.py
│ ├── order_service.py
│ └── ...
├── core/ # Core functionality
│ ├── database.py # Database connection
│ ├── config.py # Configuration
│ └── lifespan.py # Application lifecycle
└── exceptions/ # Custom exceptions
├── base.py
└── handler.py
```
## Development Workflow
### 1. Creating a New API Endpoint
**Steps**:
1. Define Pydantic schema in `models/schema/`
2. Create service method in `app/services/`
3. Add API route in `app/api/v1/`
4. Write tests in `tests/`
**Example** - Adding a new product endpoint:
```python
# Step 1: Schema (models/schema/product.py)
from pydantic import BaseModel
class ProductCreate(BaseModel):
name: str
description: str
price: float
vendor_id: int
class ProductResponse(BaseModel):
id: int
name: str
description: str
price: float
vendor_id: int
class Config:
from_attributes = True
# Step 2: Service (app/services/product_service.py)
from sqlalchemy.orm import Session
from models.database.product import Product
class ProductService:
def create_product(self, db: Session, data: ProductCreate) -> Product:
product = Product(**data.dict())
db.add(product)
db.commit()
db.refresh(product)
return product
product_service = ProductService()
# Step 3: API Route (app/api/v1/shop/products.py)
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.services.product_service import product_service
from models.schema.product import ProductCreate, ProductResponse
router = APIRouter(prefix="/products")
@router.post("/", response_model=ProductResponse)
async def create_product(
data: ProductCreate,
db: Session = Depends(get_db)
):
return product_service.create_product(db, data)
```
### 2. Creating a Service
Services contain business logic and should be reusable across routes.
**Pattern**:
```python
# app/services/example_service.py
from sqlalchemy.orm import Session
from typing import List, Optional
import logging
logger = logging.getLogger(__name__)
class ExampleService:
"""Service for handling example operations."""
def get_items(
self,
db: Session,
vendor_id: int,
skip: int = 0,
limit: int = 100
) -> List[Item]:
"""Get items for a vendor with pagination."""
try:
items = db.query(Item).filter(
Item.vendor_id == vendor_id
).offset(skip).limit(limit).all()
logger.info(f"Retrieved {len(items)} items for vendor {vendor_id}")
return items
except Exception as e:
logger.error(f"Error retrieving items: {e}")
raise
def create_item(
self,
db: Session,
vendor_id: int,
data: ItemCreate
) -> Item:
"""Create a new item."""
try:
item = Item(
vendor_id=vendor_id,
**data.dict()
)
db.add(item)
db.commit()
db.refresh(item)
logger.info(f"Created item {item.id} for vendor {vendor_id}")
return item
except Exception as e:
logger.error(f"Error creating item: {e}")
db.rollback()
raise
# Singleton instance
example_service = ExampleService()
```
### 3. Database Operations
**Best Practices**:
```python
# ✅ Good - Use service layer
@router.get("/products")
async def get_products(
vendor_id: int,
db: Session = Depends(get_db)
):
products = product_service.get_products(db, vendor_id)
return {"products": products}
# ❌ Bad - Database queries in route handler
@router.get("/products")
async def get_products(
vendor_id: int,
db: Session = Depends(get_db)
):
products = db.query(Product).filter(
Product.vendor_id == vendor_id
).all()
return {"products": products}
```
**Always Scope to Vendor**:
```python
# ✅ Good - Scoped to vendor
products = db.query(Product).filter(
Product.vendor_id == request.state.vendor_id
).all()
# ❌ Bad - Not scoped, security risk!
products = db.query(Product).all()
```
## Dependency Injection
FastAPI's dependency system provides clean code organization:
```python
from fastapi import Depends
from sqlalchemy.orm import Session
from app.core.database import get_db
from middleware.auth import auth_manager
from models.database.user import User
@router.get("/protected")
async def protected_endpoint(
current_user: User = Depends(auth_manager.get_current_user),
db: Session = Depends(get_db)
):
# current_user is automatically injected
# db session is automatically injected
return {"user": current_user.username}
```
## Error Handling
**Custom Exceptions**:
```python
from app.exceptions import (
ResourceNotFoundException,
ValidationException,
InsufficientPermissionsException
)
@router.get("/products/{product_id}")
async def get_product(
product_id: int,
db: Session = Depends(get_db)
):
product = db.query(Product).filter(Product.id == product_id).first()
if not product:
raise ResourceNotFoundException(f"Product {product_id} not found")
return product
```
**Exception Handlers** are configured globally in `app/exceptions/handler.py`
## Testing
### Unit Tests
```python
# tests/unit/services/test_product_service.py
import pytest
from app.services.product_service import ProductService
def test_create_product(db_session):
service = ProductService()
data = ProductCreate(
name="Test Product",
price=29.99,
vendor_id=1
)
product = service.create_product(db_session, data)
assert product.id is not None
assert product.name == "Test Product"
assert product.vendor_id == 1
```
### Integration Tests
```python
# tests/integration/test_product_api.py
def test_create_product_endpoint(client, auth_headers):
response = client.post(
"/api/v1/products",
json={
"name": "Test Product",
"price": 29.99,
"vendor_id": 1
},
headers=auth_headers
)
assert response.status_code == 200
data = response.json()
assert data["name"] == "Test Product"
```
## Code Quality
### Linting
```bash
# Run linters
make lint
# Auto-fix formatting
make format
```
### Type Checking
```bash
# Run mypy
make lint
```
**Use Type Hints**:
```python
from typing import List, Optional
from sqlalchemy.orm import Session
def get_items(
db: Session,
vendor_id: int,
limit: Optional[int] = None
) -> List[Item]:
query = db.query(Item).filter(Item.vendor_id == vendor_id)
if limit:
query = query.limit(limit)
return query.all()
```
## Database Migrations
**Creating a Migration**:
```bash
make migrate-create message="add_product_table"
```
**Applying Migrations**:
```bash
make migrate-up
```
**See**: [Database Migrations](../development/database-migrations.md)
## API Documentation
FastAPI automatically generates API documentation:
- **Swagger UI**: http://localhost:8000/docs
- **ReDoc**: http://localhost:8000/redoc
- **OpenAPI JSON**: http://localhost:8000/openapi.json
**Document your endpoints**:
```python
@router.post(
"/products",
response_model=ProductResponse,
summary="Create a new product",
description="Creates a new product for the authenticated vendor",
responses={
201: {"description": "Product created successfully"},
400: {"description": "Invalid product data"},
401: {"description": "Not authenticated"},
403: {"description": "Not authorized to create products"}
}
)
async def create_product(
data: ProductCreate,
current_user: User = Depends(auth_manager.require_vendor),
db: Session = Depends(get_db)
):
"""
Create a new product.
- **name**: Product name (required)
- **description**: Product description
- **price**: Product price (required)
- **vendor_id**: Vendor ID (required)
"""
return product_service.create_product(db, current_user.id, data)
```
## Configuration
**Environment Variables** (`.env`):
```bash
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# JWT
JWT_SECRET_KEY=your-secret-key
JWT_EXPIRE_MINUTES=30
# Application
ENVIRONMENT=development
DEBUG=true
LOG_LEVEL=INFO
```
**Accessing Config**:
```python
from app.core.config import settings
database_url = settings.database_url
secret_key = settings.jwt_secret_key
```
## Logging
**Use Structured Logging**:
```python
import logging
logger = logging.getLogger(__name__)
def process_order(order_id: int):
logger.info(f"Processing order", extra={"order_id": order_id})
try:
# Process order
logger.info(f"Order processed successfully", extra={"order_id": order_id})
except Exception as e:
logger.error(
f"Order processing failed",
extra={"order_id": order_id, "error": str(e)},
exc_info=True
)
raise
```
## Performance
### Database Query Optimization
```python
# ✅ Good - Use joins to avoid N+1 queries
from sqlalchemy.orm import joinedload
products = db.query(Product).options(
joinedload(Product.category),
joinedload(Product.vendor)
).filter(Product.vendor_id == vendor_id).all()
# ❌ Bad - N+1 query problem
products = db.query(Product).filter(
Product.vendor_id == vendor_id
).all()
for product in products:
category = product.category # Additional query!
```
### Caching
```python
from functools import lru_cache
@lru_cache(maxsize=100)
def get_vendor_settings(vendor_id: int) -> dict:
# Expensive operation cached in memory
return db.query(VendorSettings).filter(
VendorSettings.vendor_id == vendor_id
).first()
```
## Security Best Practices
1. **Always validate input** with Pydantic schemas
2. **Always scope queries** to vendor/user
3. **Use parameterized queries** (SQLAlchemy ORM does this)
4. **Never log sensitive data** (passwords, tokens, credit cards)
5. **Use HTTPS** in production
6. **Implement rate limiting** on sensitive endpoints
7. **Validate file uploads** (type, size, content)
8. **Sanitize user input** before rendering in templates
## Next Steps
- [Middleware Reference](middleware-reference.md) - Technical API documentation
- [Architecture Overview](../architecture/overview.md) - System architecture
- [Database Migrations](../development/database-migrations.md) - Migration guide
- [Testing Guide](../testing/testing-guide.md) - Testing practices

View File

@@ -341,13 +341,13 @@ def test_owner_has_all_permissions():
def test_create_product_with_permission(client): def test_create_product_with_permission(client):
user = create_user_with_permission("products.create") user = create_user_with_permission("products.create")
token = create_token(user) token = create_token(user)
response = client.post( response = client.post(
"/api/v1/vendor/ACME/products", "/api/v1/vendor/ACME/products",
json={"name": "Test"}, json={"name": "Test"},
headers={"Authorization": f"Bearer {token}"} headers={"Authorization": f"Bearer {token}"}
) )
assert response.status_code == 201 assert response.status_code == 201
``` ```
@@ -510,4 +510,4 @@ ENVIRONMENT=development|staging|production
**Print and keep at your desk!** **Print and keep at your desk!**
For full documentation: See `RBAC_DEVELOPER_GUIDE.md` For full documentation: See [RBAC Developer Guide](../api/RBAC.md)

View File

@@ -324,5 +324,5 @@ Migration failures will halt deployment to prevent data corruption.
## Further Reading ## Further Reading
- [Alembic Official Documentation](https://alembic.sqlalchemy.org/) - [Alembic Official Documentation](https://alembic.sqlalchemy.org/)
- [Database Schema Documentation](database-schema.md) - [Database Setup Guide](../getting-started/DATABASE_SETUP_GUIDE.md)
- [Deployment Guide](../deployment/production.md) - [Deployment Guide](../deployment/production.md)

View File

@@ -1,7 +1,7 @@
# Environment Detection System # Environment Detection System
**Version:** 1.0 **Version:** 1.0
**Last Updated:** November 2024 **Last Updated:** November 2025
**Audience:** Development Team **Audience:** Development Team
--- ---
@@ -21,7 +21,7 @@
## Overview ## Overview
The LetzShop platform uses **automatic environment detection** to determine the runtime environment (development, staging, or production) and adjust security settings accordingly. The Wizamart platform uses **automatic environment detection** to determine the runtime environment (development, staging, or production) and adjust security settings accordingly.
### Why Auto-Detection? ### Why Auto-Detection?
@@ -335,7 +335,7 @@ ExecStart=/usr/bin/uvicorn main:app --host 0.0.0.0 --port 8000
```yaml ```yaml
services: services:
web: web:
image: letzshop:latest image: wizamart:latest
environment: environment:
- ENV=production - ENV=production
ports: ports:
@@ -519,7 +519,7 @@ def debug_environment():
```bash ```bash
# Clone repo # Clone repo
git clone <repo-url> git clone <repo-url>
cd letzshop cd wizamart
# Create virtual environment # Create virtual environment
python -m venv venv python -m venv venv
@@ -553,7 +553,7 @@ services:
build: . build: .
environment: environment:
- ENV=staging - ENV=staging
- DATABASE_URL=postgresql://user:pass@db:5432/letzshop_staging - DATABASE_URL=postgresql://user:pass@db:5432/wizamart_staging
ports: ports:
- "8000:8000" - "8000:8000"
depends_on: depends_on:
@@ -562,7 +562,7 @@ services:
db: db:
image: postgres:15 image: postgres:15
environment: environment:
- POSTGRES_DB=letzshop_staging - POSTGRES_DB=wizamart_staging
- POSTGRES_USER=user - POSTGRES_USER=user
- POSTGRES_PASSWORD=pass - POSTGRES_PASSWORD=pass
``` ```
@@ -572,13 +572,13 @@ services:
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: letzshop-staging name: wizamart-staging
spec: spec:
template: template:
spec: spec:
containers: containers:
- name: web - name: web
image: letzshop:latest image: wizamart:latest
env: env:
- name: ENV - name: ENV
value: "staging" value: "staging"
@@ -599,15 +599,15 @@ spec:
**systemd Service:** **systemd Service:**
```ini ```ini
[Unit] [Unit]
Description=LetzShop Platform Description=Wizamart Platform
After=network.target After=network.target
[Service] [Service]
User=letzshop User=wizamart
WorkingDirectory=/opt/letzshop WorkingDirectory=/opt/wizamart
Environment="ENV=production" Environment="ENV=production"
Environment="DATABASE_URL=postgresql://user:pass@localhost/letzshop_prod" Environment="DATABASE_URL=postgresql://user:pass@localhost/wizamart_prod"
ExecStart=/opt/letzshop/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 ExecStart=/opt/wizamart/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
Restart=always Restart=always
[Install] [Install]
@@ -1098,9 +1098,9 @@ response.set_cookie(secure=should_use_secure_cookies())
### Related Documentation ### Related Documentation
- [Authentication System Documentation](AUTHENTICATION_SYSTEM_DOCS.md) - [Authentication System Documentation](../api/authentication.md)
- [Deployment Guide](DEPLOYMENT.md) - [Deployment Guide](../deployment/production.md)
- [Security Best Practices](SECURITY.md) - [Exception Handling](exception-handling.md)
### External References ### External References

View File

@@ -1,8 +1,14 @@
# Multi-Tenant Ecommerce Platform - Complete Naming Convention Guide # Naming Conventions
**Version:** 1.0
**Last Updated:** November 2025
**Audience:** Development Team
---
## Overview ## Overview
This document establishes consistent naming conventions across the entire multi-tenant ecommerce platform. Consistent naming improves code readability, reduces developer confusion, and ensures maintainable architecture. This document establishes consistent naming conventions across the entire Wizamart multi-tenant ecommerce platform. Consistent naming improves code readability, reduces developer confusion, and ensures maintainable architecture.
## Core Principles ## Core Principles
@@ -22,9 +28,12 @@ This document establishes consistent naming conventions across the entire multi-
- **Service files**: `entity_service.py` (singular + service) - **Service files**: `entity_service.py` (singular + service)
- **Exception files**: `entity.py` (singular domain) - **Exception files**: `entity.py` (singular domain)
---
## Detailed Naming Rules ## Detailed Naming Rules
### API Endpoint Files (PLURAL) ### API Endpoint Files (PLURAL)
**Rule**: API files handle collections of resources, so use plural names. **Rule**: API files handle collections of resources, so use plural names.
**Location**: `app/api/v1/*/` **Location**: `app/api/v1/*/`
@@ -53,6 +62,7 @@ app/api/v1/public/vendors/
**Rationale**: REST endpoints typically operate on collections (`GET /products`, `POST /orders`). **Rationale**: REST endpoints typically operate on collections (`GET /products`, `POST /orders`).
### Database Model Files (SINGULAR) ### Database Model Files (SINGULAR)
**Rule**: Model files represent individual entity definitions, so use singular names. **Rule**: Model files represent individual entity definitions, so use singular names.
**Location**: `models/database/` **Location**: `models/database/`
@@ -76,7 +86,7 @@ models/database/
class Product(Base): # Singular class Product(Base): # Singular
class ProductVariant(Base): # Singular class ProductVariant(Base): # Singular
# models/database/inventory.py # models/database/inventory.py
class Inventory(Base): # Singular class Inventory(Base): # Singular
class InventoryMovement(Base): # Singular class InventoryMovement(Base): # Singular
``` ```
@@ -84,6 +94,7 @@ class InventoryMovement(Base): # Singular
**Rationale**: Each model class represents a single entity instance in the database. **Rationale**: Each model class represents a single entity instance in the database.
### Schema/Pydantic Model Files (SINGULAR) ### Schema/Pydantic Model Files (SINGULAR)
**Rule**: Schema files define validation for individual entities, so use singular names. **Rule**: Schema files define validation for individual entities, so use singular names.
**Location**: `models/schema/` **Location**: `models/schema/`
@@ -112,6 +123,7 @@ class ProductResponse(BaseModel): # Singular entity
**Rationale**: Schema models validate individual entity data structures. **Rationale**: Schema models validate individual entity data structures.
### Service Files (SINGULAR + "service") ### Service Files (SINGULAR + "service")
**Rule**: Service files handle business logic for one domain area, so use singular + "service". **Rule**: Service files handle business logic for one domain area, so use singular + "service".
**Location**: `services/` **Location**: `services/`
@@ -142,6 +154,7 @@ class ProductService: # Singular domain focus
**Rationale**: Each service focuses on one business domain area. **Rationale**: Each service focuses on one business domain area.
### Exception Files (SINGULAR) ### Exception Files (SINGULAR)
**Rule**: Exception files handle errors for one domain area, so use singular names. **Rule**: Exception files handle errors for one domain area, so use singular names.
**Location**: `app/exceptions/` **Location**: `app/exceptions/`
@@ -172,6 +185,7 @@ class ProductValidationException(ValidationException):
**Rationale**: Exception files are domain-focused, not collection-focused. **Rationale**: Exception files are domain-focused, not collection-focused.
### Middleware Files (DESCRIPTIVE) ### Middleware Files (DESCRIPTIVE)
**Rule**: Middleware files use descriptive names based on their function. **Rule**: Middleware files use descriptive names based on their function.
**Location**: `middleware/` **Location**: `middleware/`
@@ -189,6 +203,7 @@ middleware/
**Rationale**: Middleware serves specific cross-cutting functions. **Rationale**: Middleware serves specific cross-cutting functions.
### Frontend Files ### Frontend Files
**Rule**: Frontend files use context-appropriate naming. **Rule**: Frontend files use context-appropriate naming.
**Location**: `frontend/` **Location**: `frontend/`
@@ -212,11 +227,13 @@ frontend/
└── cart.html # SINGULAR - one shopping cart └── cart.html # SINGULAR - one shopping cart
``` ```
**Rationale**: **Rationale**:
- List views are plural (show collections) - List views are plural (show collections)
- Detail views are singular (show individual items) - Detail views are singular (show individual items)
- Functional views use descriptive names - Functional views use descriptive names
---
## Terminology Standards ## Terminology Standards
### Core Business Terms ### Core Business Terms
@@ -235,12 +252,12 @@ frontend/
**Table Names**: Use singular, lowercase with underscores **Table Names**: Use singular, lowercase with underscores
```sql ```sql
-- Correct -- Correct
inventory inventory
inventory_movements inventory_movements
vendor_users vendor_users
-- Incorrect -- Incorrect
inventories inventories
inventory_movement inventory_movement
vendorusers vendorusers
@@ -248,12 +265,12 @@ vendorusers
**Column Names**: Use singular, descriptive names **Column Names**: Use singular, descriptive names
```sql ```sql
-- Correct -- Correct
vendor_id vendor_id
inventory_level inventory_level
created_at created_at
-- Incorrect -- Incorrect
vendors_id vendors_id
inventory_levels inventory_levels
creation_time creation_time
@@ -283,42 +300,47 @@ POST /api/v1/vendor/auth/login # Authentication
GET /api/v1/vendor/settings # Vendor settings GET /api/v1/vendor/settings # Vendor settings
``` ```
---
## Variable and Function Naming ## Variable and Function Naming
### Function Names ### Function Names
```python ```python
# Correct - verb + singular object # Correct - verb + singular object
def create_product() def create_product()
def get_customer() def get_customer()
def update_order() def update_order()
def delete_inventory_item() def delete_inventory_item()
# Correct - verb + plural when operating on collections # Correct - verb + plural when operating on collections
def get_products() def get_products()
def list_customers() def list_customers()
def bulk_update_orders() def bulk_update_orders()
# Incorrect # Incorrect
def create_products() # Creates one product def create_products() # Creates one product
def get_customers() # Gets one customer def get_customers() # Gets one customer
``` ```
### Variable Names ### Variable Names
```python ```python
# Correct - context-appropriate singular/plural # Correct - context-appropriate singular/plural
product = get_product(id) product = get_product(id)
products = get_products() products = get_products()
customer_list = get_all_customers() customer_list = get_all_customers()
inventory_count = len(inventory_items) inventory_count = len(inventory_items)
# Incorrect # Incorrect
products = get_product(id) # Single item, should be singular products = get_product(id) # Single item, should be singular
product = get_products() # Multiple items, should be plural product = get_products() # Multiple items, should be plural
``` ```
### Class Attributes ### Class Attributes
```python ```python
# Correct - descriptive and consistent # Correct - descriptive and consistent
class Vendor: class Vendor:
id: int id: int
name: str name: str
@@ -332,36 +354,7 @@ class Customer:
last_order_date: datetime # Most recent last_order_date: datetime # Most recent
``` ```
## Migration Checklist ---
When applying these naming conventions to existing code:
### File Renames Required
- [ ] `app/api/v1/stock.py``app/api/v1/inventory.py`
- [ ] `models/database/stock.py``models/database/inventory.py`
- [ ] `models/schema/stock.py``models/schema/inventory.py`
- [ ] `services/stock_service.py``services/inventory_service.py`
- [ ] `app/exceptions/stock.py``app/exceptions/inventory.py`
### Import Statement Updates
- [ ] Update all `from models.database.stock import` statements
- [ ] Update all `from services.stock_service import` statements
- [ ] Update all `stock_` variable prefixes to `inventory_`
### Class Name Updates
- [ ] `Stock``Inventory`
- [ ] `StockMovement``InventoryMovement`
- [ ] `StockService``InventoryService`
### Database Updates
- [ ] Rename `stock` table to `inventory`
- [ ] Rename `stock_movements` table to `inventory_movements`
- [ ] Update all `stock_id` foreign keys to `inventory_id`
### Frontend Updates
- [ ] Update all HTML files with stock terminology
- [ ] Update JavaScript variable names
- [ ] Update CSS class names if applicable
## Benefits of Consistent Naming ## Benefits of Consistent Naming
@@ -373,9 +366,12 @@ When applying these naming conventions to existing code:
6. **Documentation**: Consistent terminology across all documentation 6. **Documentation**: Consistent terminology across all documentation
7. **API Usability**: Predictable and intuitive API endpoint structures 7. **API Usability**: Predictable and intuitive API endpoint structures
---
## Enforcement ## Enforcement
### Code Review Checklist ### Code Review Checklist
- [ ] File names follow singular/plural conventions - [ ] File names follow singular/plural conventions
- [ ] Class names use appropriate terminology (inventory vs stock) - [ ] Class names use appropriate terminology (inventory vs stock)
- [ ] API endpoints use plural resource names - [ ] API endpoints use plural resource names
@@ -383,10 +379,45 @@ When applying these naming conventions to existing code:
- [ ] Variables names match their content (singular vs plural) - [ ] Variables names match their content (singular vs plural)
### Automated Checks ### Automated Checks
Consider implementing linting rules or pre-commit hooks to enforce: Consider implementing linting rules or pre-commit hooks to enforce:
- File naming patterns - File naming patterns
- Import statement consistency - Import statement consistency
- Variable naming conventions - Variable naming conventions
- API endpoint patterns - API endpoint patterns
This naming convention guide ensures consistent, maintainable, and intuitive code across the entire multi-tenant ecommerce platform. ---
## Quick Reference Table
| Component | Naming | Example |
|-----------|--------|---------|
| API Endpoint Files | PLURAL | `products.py`, `orders.py` |
| Database Models | SINGULAR | `product.py`, `order.py` |
| Schema/Pydantic | SINGULAR | `product.py`, `order.py` |
| Services | SINGULAR + service | `product_service.py` |
| Exceptions | SINGULAR | `product.py`, `order.py` |
| Middleware | DESCRIPTIVE | `auth.py`, `rate_limiter.py` |
| Database Tables | SINGULAR | `product`, `inventory` |
| Database Columns | SINGULAR | `vendor_id`, `created_at` |
| API Endpoints | PLURAL | `/products`, `/orders` |
| Functions (single) | SINGULAR | `create_product()` |
| Functions (multiple) | PLURAL | `get_products()` |
| Variables (single) | SINGULAR | `product = ...` |
| Variables (multiple) | PLURAL | `products = ...` |
---
## Related Documentation
- [Contributing Guide](contributing.md) - Development workflow
- [Backend Development](../backend/overview.md) - Backend development guide
- [Architecture Overview](../architecture/overview.md) - System architecture
---
**Document Version:** 1.0
**Last Updated:** November 2025
**Maintained By:** Backend Team
This naming convention guide ensures consistent, maintainable, and intuitive code across the entire Wizamart multi-tenant ecommerce platform.

View File

@@ -1 +1,398 @@
*This documentation is under development.* # PyCharm Troubleshooting Guide
Common PyCharm issues and their solutions for the development team.
---
## Table of Contents
- [Go to Declaration Not Working (Ctrl+B)](#go-to-declaration-not-working-ctrlb)
- [Import Errors and Red Underlines](#import-errors-and-red-underlines)
- [Python Interpreter Issues](#python-interpreter-issues)
- [Debugging Not Working](#debugging-not-working)
- [Performance Issues](#performance-issues)
- [Git Integration Issues](#git-integration-issues)
- [Database Tool Issues](#database-tool-issues)
---
## Go to Declaration Not Working (Ctrl+B)
### Problem
When you press **Ctrl+B** (or Cmd+B on Mac) on a function, class, or variable, PyCharm shows:
- "Cannot find declaration to go to"
- No navigation occurs
- Imports are marked as unresolved even though code runs fine
**Example:**
```python
from app.services.admin_audit_service import admin_audit_service
# Ctrl+B on get_audit_logs doesn't work
logs = admin_audit_service.get_audit_logs(db, filters)
```
### Solution 1: Configure Source Roots (Most Common Fix)
PyCharm needs to know your project structure:
1. **Mark project root as Sources Root:**
- Right-click on project root folder (`letzshop-product-import`)
- Select **"Mark Directory as"** → **"Sources Root"**
- The folder icon should turn blue
2. **Via Project Structure settings:**
- **File** → **Settings** (Ctrl+Alt+S)
- Navigate to **Project: letzshop-product-import****Project Structure**
- Select your project root directory
- Click the **"Sources"** button at the top
- Click **Apply****OK**
3. **Verify the change:**
- Your project root should now have a blue folder icon
- The `.idea` folder should show the source root is configured
### Solution 2: Verify Python Interpreter
Ensure PyCharm is using the correct virtual environment:
1. **Check current interpreter:**
- **File** → **Settings****Project****Python Interpreter**
- Should show: `/home/samir/PycharmProjects/letzshop-product-import/.venv/bin/python`
2. **If wrong interpreter:**
- Click the gear icon → **Add**
- Select **"Existing environment"**
- Navigate to: `[PROJECT_ROOT]/.venv/bin/python`
- Click **OK**
3. **Wait for indexing:**
- Bottom right corner will show "Indexing..."
- Wait for it to complete before testing
### Solution 3: Invalidate Caches
PyCharm's index might be corrupted:
1. **File****Invalidate Caches...**
2. Select all checkboxes:
- ✅ Invalidate and Restart
- ✅ Clear file system cache and Local History
- ✅ Clear downloaded shared indexes
3. Click **"Invalidate and Restart"**
4. **Wait 2-5 minutes** for re-indexing after restart
### Solution 4: Reimport Project
If nothing else works, rebuild the project structure:
1. **Close PyCharm**
2. **Delete PyCharm's cache folder:**
```bash
rm -rf .idea/
```
3. **Reopen project in PyCharm:**
- **File** → **Open**
- Select project folder
- Let PyCharm recreate `.idea/` folder
4. **Reconfigure:**
- Set Python interpreter
- Mark source roots
- Wait for indexing
### Solution 5: Check .idea/workspace.xml
Sometimes workspace configuration gets corrupted:
```bash
# Close PyCharm first
rm .idea/workspace.xml
# Reopen PyCharm - it will regenerate this file
```
---
## Import Errors and Red Underlines
### Problem
Imports show red underlines and errors like:
- "Unresolved reference"
- "No module named 'app'"
- Code runs fine but PyCharm shows errors
### Solution
1. **Check Sources Root** (see above)
2. **Verify `__init__.py` files exist:**
```bash
# All package directories need __init__.py
ls -la app/__init__.py
ls -la app/services/__init__.py
ls -la app/api/__init__.py
```
3. **Add project root to PYTHONPATH:**
- **File** → **Settings** → **Project** → **Project Structure**
- Right-click project root → **"Mark as Sources Root"**
4. **Enable namespace packages** (if using PEP 420):
- **File** → **Settings** → **Editor** → **Code Style** → **Python**
- Check "Support namespace packages"
---
## Python Interpreter Issues
### Problem
- "No Python interpreter configured"
- Wrong Python version
- Missing packages even though they're installed
### Solution
1. **Verify virtual environment:**
```bash
# In terminal
which python # Should show .venv/bin/python
python --version # Should show Python 3.13.x
```
2. **Configure in PyCharm:**
- **File** → **Settings** → **Project** → **Python Interpreter**
- Click gear icon → **Add**
- Select **"Existing Environment"**
- Choose: `[PROJECT_ROOT]/.venv/bin/python`
3. **Refresh interpreter packages:**
- In Python Interpreter settings
- Click refresh icon (⟳)
- Wait for packages to reload
4. **Reinstall packages if needed:**
```bash
source .venv/bin/activate
pip install -r requirements.txt
pip install -r requirements-dev.txt
```
---
## Debugging Not Working
### Problem
- Breakpoints are grayed out
- Debugger doesn't stop at breakpoints
- "Debugger is not attached"
### Solution 1: Check Run Configuration
1. **Edit configuration:**
- Top right → Edit Configurations
- Should use "Python" configuration type
- Script path should point to your main file
- Interpreter should be your virtual environment
2. **For FastAPI/Uvicorn:**
- Module: `uvicorn`
- Parameters: `main:app --reload`
- Working directory: Project root
### Solution 2: Enable Gevent/Asyncio Support
For async code (FastAPI):
1. **File** → **Settings** → **Build, Execution, Deployment** → **Python Debugger**
2. Check **"Gevent compatible"** or **"Asyncio compatible"**
3. Restart debugger
### Solution 3: Check Breakpoint Settings
Right-click on breakpoint (red dot):
- Ensure "Enabled" is checked
- Check "Suspend" is set to "All"
- Remove any conditions if not needed
---
## Performance Issues
### Problem
- PyCharm is slow or freezing
- High CPU/memory usage
- Indexing takes forever
### Solution 1: Exclude Unnecessary Directories
Exclude directories PyCharm doesn't need to index:
1. **File** → **Settings** → **Project** → **Project Structure**
2. **Mark as "Excluded":**
- `.venv` (if not already)
- `node_modules`
- `.pytest_cache`
- `__pycache__`
- `htmlcov`
- `site` (mkdocs build output)
3. **Right-click these folders in Project view:**
- **Mark Directory as** → **Excluded**
### Solution 2: Increase Memory
1. **Help** → **Change Memory Settings**
2. Increase heap size to **2048 MB** or higher
3. Restart PyCharm
### Solution 3: Disable Unused Plugins
1. **File** → **Settings** → **Plugins**
2. Disable plugins you don't use
3. Keep essential: Python, Git, Database, Markdown
4. Restart PyCharm
### Solution 4: Pause Indexing
If you need to work immediately:
1. **File** → **Pause Indexing**
2. Do your work
3. **Resume Indexing** when convenient
---
## Git Integration Issues
### Problem
- Git operations fail
- "Cannot run git" errors
- Changes not detected
### Solution 1: Configure Git Executable
1. **File** → **Settings** → **Version Control** → **Git**
2. **Path to Git executable:**
- Linux/Mac: `/usr/bin/git`
- Windows: `C:\Program Files\Git\bin\git.exe`
3. Click **"Test"** to verify
4. Click **OK**
### Solution 2: Check Git Root
1. **File** → **Settings** → **Version Control**
2. Verify your project root is listed
3. If missing, click **+** and add it
### Solution 3: Refresh VCS
1. **VCS** → **Refresh File Status**
2. Or: **Git** → **Refresh**
3. Or press: **Ctrl+F5**
---
## Database Tool Issues
### Problem
- Can't connect to PostgreSQL database
- "Connection refused" errors
- Tables not showing
### Solution 1: Verify Database Connection
1. **Database** tool window (usually right side)
2. Click **+** → **Data Source** → **PostgreSQL**
3. **Configure:**
```
Host: localhost
Port: 5432
Database: letzshop_db
User: [from .env file]
Password: [from .env file]
```
4. Click **"Test Connection"**
5. Download drivers if prompted
### Solution 2: Check Database is Running
```bash
# Check if PostgreSQL is running
sudo systemctl status postgresql
# Start if not running
sudo systemctl start postgresql
```
### Solution 3: Verify .env Configuration
```bash
# Check your .env file
cat .env | grep DATABASE
# Should show something like:
DATABASE_URL=postgresql://user:password@localhost:5432/letzshop_db
```
---
## Quick Checklist for New Developers
When setting up PyCharm for the first time:
- [ ] Clone repository
- [ ] Create virtual environment: `python -m venv .venv`
- [ ] Activate: `source .venv/bin/activate`
- [ ] Install dependencies: `pip install -r requirements.txt`
- [ ] Open project in PyCharm
- [ ] Configure Python interpreter (`.venv/bin/python`)
- [ ] Mark project root as Sources Root
- [ ] Exclude `.venv`, `.pytest_cache`, `__pycache__` directories
- [ ] Configure Git executable
- [ ] Configure database connection
- [ ] Wait for indexing to complete
- [ ] Test: Ctrl+B on an import should work
---
## Still Having Issues?
### Check PyCharm Logs
1. **Help** → **Show Log in Explorer/Finder**
2. Look for errors in `idea.log`
3. Search for "ERROR" or "Exception"
### Enable Debug Logging
1. **Help** → **Diagnostic Tools** → **Debug Log Settings**
2. Add: `#com.intellij.python`
3. Reproduce the issue
4. Check logs
### Report to Team
If the issue persists:
1. Screenshot the error
2. Share PyCharm version: **Help** → **About**
3. Share Python version: `python --version`
4. Share OS: `uname -a` (Linux/Mac) or `ver` (Windows)
5. Post in team chat with:
- What you were trying to do
- What error you got
- What you've tried so far
---
## Related Documentation
- [PyCharm Make Configuration](pycharm-configuration-make.md) - Configure Make with virtual environment
- [Contributing Guide](contributing.md) - Development workflow
- [Database Migrations](database-migrations.md) - Working with Alembic

501
docs/frontend/overview.md Normal file
View File

@@ -0,0 +1,501 @@
# Frontend Architecture Overview
**Version:** 1.0
**Last Updated:** November 2025
**Audience:** Frontend Developers
---
## What is This Document?
This document provides a comprehensive overview of the Wizamart frontend architecture, covering all three distinct frontend applications and the shared design patterns that ensure consistency, maintainability, and developer productivity across the entire platform.
This serves as the introduction to three detailed architecture documents:
1. Admin Frontend Architecture
2. Vendor Frontend Architecture
3. Shop Frontend Architecture
---
## Platform Overview
Wizamart is a multi-tenant e-commerce marketplace platform with three distinct frontend applications, each serving different user groups:
```
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ ADMIN │ │ VENDOR │ │ SHOP │ │
│ │ FRONTEND │ │ FRONTEND │ │ FRONTEND │ │
│ └──────────────┘ └──────────────┘ └────────────┘ │
│ │ │ │ │
│ ├────────────────────────┴─────────────────────┤ │
│ │ │ │
│ │ SHARED ARCHITECTURE │ │
│ │ • Alpine.js │ │
│ │ • Jinja2 Templates │ │
│ │ • Tailwind CSS │ │
│ │ • FastAPI Backend │ │
│ │ • Design Patterns │ │
│ │ │ │
│ └──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## Three Frontends Explained
### 1. Admin Frontend
**Purpose:** Platform administration and control
**Users:** Platform administrators
**Access:** `/admin/*`
**Auth:** Admin role required (`is_admin=True`)
**Key Features:**
- Vendor management (create, verify, suspend)
- User management (roles, permissions)
- Platform-wide analytics and monitoring
- Theme customization for vendors
- Import job monitoring
- Audit log viewing
- System settings and configuration
**UI Theme:** Windmill Dashboard (professional admin UI)
**Colors:** Purple (#7c3aed) primary
**Pages:**
- `/admin/dashboard`
- `/admin/vendors`
- `/admin/vendors/{code}/edit`
- `/admin/vendors/{code}/theme`
- `/admin/users`
- `/admin/products`
- `/admin/orders`
- `/admin/import-jobs`
- `/admin/audit-logs`
- `/admin/settings`
---
### 2. Vendor Frontend
**Purpose:** Vendor shop management and operations
**Users:** Vendor owners and their team members
**Access:** `/vendor/{vendor_code}/*`
**Auth:** Vendor role required + vendor ownership
**Key Features:**
- Product catalog management
- Inventory tracking
- Order management
- Customer management
- Marketplace imports (Amazon, eBay, etc.)
- Shop analytics and reports
- Team member management
- Shop settings
**UI Theme:** Windmill Dashboard (professional admin UI)
**Colors:** Purple (#7c3aed) primary
**Pages:**
- `/vendor/{code}/dashboard`
- `/vendor/{code}/products`
- `/vendor/{code}/inventory`
- `/vendor/{code}/orders`
- `/vendor/{code}/customers`
- `/vendor/{code}/marketplace`
- `/vendor/{code}/analytics`
- `/vendor/{code}/team`
- `/vendor/{code}/settings`
---
### 3. Shop Frontend
**Purpose:** Customer-facing e-commerce storefront
**Users:** Customers and visitors
**Access:** Vendor-specific domains or subdomains
**Auth:** Optional (guest checkout supported)
**Key Features:**
- Product browsing and search
- Shopping cart management
- Checkout and payment
- Order tracking
- Customer account (optional)
- Wishlist (optional)
- Product reviews (optional)
- Multi-theme system (vendor branding)
**UI Theme:** Custom per vendor (multi-theme system)
**Colors:** Vendor-specific via CSS variables
**Special Features:**
- Vendor Context Middleware (domain → vendor detection)
- Theme Context Middleware (loads vendor theme)
- CSS Variables for dynamic theming
- Client-side cart (localStorage)
**Pages:**
- `/` (homepage)
- `/products` (catalog)
- `/products/{id}` (product detail)
- `/category/{slug}` (category browse)
- `/search` (search results)
- `/cart` (shopping cart)
- `/checkout` (checkout flow)
- `/account` (customer account)
- `/orders` (order history)
- `/about` (about vendor)
- `/contact` (contact form)
---
## Shared Technology Stack
All three frontends share the same core technologies:
| Layer | Technology | Purpose |
|-------|-----------|---------|
| Backend | FastAPI | REST API + routing |
| Templates | Jinja2 | Server-side rendering |
| Interactivity | Alpine.js 3.x | Client-side reactivity |
| Styling | Tailwind CSS 2.x | Utility-first CSS |
| Icons | Heroicons | SVG icon system |
| HTTP Client | Fetch API | API requests |
| State Management | Alpine.js reactive | No external state lib |
| Logging | Custom LogConfig | Centralized logging |
| Error Handling | Custom exceptions | Structured errors |
### Why This Stack?
- ✅ Minimal JavaScript complexity (no React/Vue build process)
- ✅ Server-side rendering for SEO
- ✅ Progressive enhancement (works without JS)
- ✅ Fast development iteration
- ✅ Small bundle sizes
- ✅ Easy to learn and maintain
- ✅ Python developers can contribute to frontend
---
## Architecture Philosophy
### 1. API-First Design
- Routes only render templates (no business logic)
- ALL data loaded client-side via REST APIs
- Clear separation: `pages.py` (templates) vs other API files
- Enables future mobile apps or SPA migrations
### 2. Progressive Enhancement
- HTML works without JavaScript (basic functionality)
- JavaScript enhances experience (filters, live updates)
- Graceful degradation for older browsers
- Accessible by default
### 3. Component-Based Templates
- Base templates provide layout
- Pages extend base templates
- Partials for reusable components
- Block overrides for customization
### 4. Centralized Design Patterns
- Shared utilities (logging, API client, utils)
- Consistent error handling across frontends
- Standardized state management patterns
- Common UI components and patterns
### 5. Developer Experience
- Copy-paste templates for new pages
- Consistent patterns reduce cognitive load
- Comprehensive documentation
- Clear file organization
---
## File Organization
The project follows a clear, frontend-specific organization:
```
app/
├── templates/
│ ├── admin/ ← Admin frontend templates
│ │ ├── base.html
│ │ ├── dashboard.html
│ │ └── partials/
│ ├── vendor/ ← Vendor frontend templates
│ │ ├── base.html
│ │ ├── dashboard.html
│ │ └── partials/
│ └── shop/ ← Shop frontend templates
│ ├── base.html
│ ├── home.html
│ └── partials/
├── static/
│ ├── admin/ ← Admin-specific assets
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ ├── vendor/ ← Vendor-specific assets
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ ├── shop/ ← Shop-specific assets
│ │ ├── css/
│ │ ├── js/
│ │ └── img/
│ └── shared/ ← Shared across all frontends
│ ├── js/
│ │ ├── log-config.js ← Centralized logging
│ │ ├── api-client.js ← HTTP client wrapper
│ │ ├── icons.js ← Icon registry
│ │ └── utils.js ← Utility functions
│ └── css/
├── api/v1/
│ ├── admin/ ← Admin API endpoints
│ │ ├── pages.py ← Routes (templates only)
│ │ ├── vendors.py ← Business logic
│ │ ├── users.py
│ │ └── ...
│ ├── vendor/ ← Vendor API endpoints
│ │ ├── pages.py
│ │ ├── products.py
│ │ └── ...
│ └── shop/ ← Shop API endpoints
│ ├── pages.py
│ ├── products.py
│ └── ...
└── exceptions/ ← Custom exception classes
├── base.py ← Base exception classes
├── admin.py ← Admin-specific exceptions
├── shop.py ← Shop-specific exceptions
├── product.py ← Product exceptions
└── handler.py ← Exception handler setup
```
---
## Request Flow
### Page Load Flow
1. **Browser Request**
2. **FastAPI Route Handler** (`pages.py`)
- Verify authentication
- Extract route parameters
- Render Jinja2 template
3. **Template Rendering**
- Extend base template
- Include partials
- Inject server-side data (user, vendor, theme)
4. **Browser Receives HTML**
- Load CSS (Tailwind)
- Load JavaScript (Alpine.js, page scripts)
5. **Alpine.js Initialization**
- `x-data` component initialized
- `...data()` spreads base state
- `init()` method runs
- Initialization guard checked
6. **Client-Side Data Loading**
- JavaScript calls REST API
- apiClient handles request
- JSON response received
7. **Reactive Updates**
- Alpine.js updates reactive state
- DOM automatically updates
- Page fully interactive
### User Interaction Flow
1. **User Action** (click, input, etc.)
2. **Alpine.js Event Handler**
- `@click`, `@input`, `@change`, etc.
- Calls component method
3. **Business Logic**
- Validate input
- Update local state
- Call API if needed
4. **API Request** (if needed)
- `apiClient.post/put/delete`
- Automatic error handling
- Logging
5. **Update State**
- Modify reactive data
- Alpine.js watches changes
6. **DOM Updates**
- Automatic reactive updates
- No manual DOM manipulation
7. **User Feedback**
- Toast notification
- Loading indicator removed
- Success/error message
---
## Core Design Patterns
For detailed information about the design patterns used across all frontends, see:
- **[Shared UI Components](shared/ui-components.md)** - Reusable UI components
- **[Pagination System](shared/pagination.md)** - Pagination implementation
- **[Sidebar Navigation](shared/sidebar.md)** - Sidebar setup guide
- **[Logging System](shared/logging.md)** - Frontend logging configuration
- **[Icons Guide](../development/icons_guide.md)** - Icon system usage
---
## Security Patterns
### Authentication & Authorization
1. **JWT Tokens**
- Stored in localStorage
- Automatically sent with API requests
- Expiration handling
2. **Role-Based Access**
- Admin: Full platform access
- Vendor: Limited to own shop
- Customer: Public + account access
3. **Route Protection**
- FastAPI dependencies verify auth
- Middleware for vendor context
- Automatic redirects to login
### Input Validation
1. **Client-Side** (Alpine.js)
- Immediate feedback
- Better UX
- Reduces server load
2. **Server-Side** (Pydantic)
- Security boundary
- Type validation
- Cannot be bypassed
3. **Both Required**
- Client-side for UX
- Server-side for security
### XSS Prevention
- Jinja2 auto-escapes by default
- Use `| safe` only when necessary
- Sanitize user content
### CSRF Protection
- Token-based (if using forms)
- SameSite cookies
- API uses Bearer tokens (CSRF-safe)
---
## Learning Path
### For New Developers
**Week 1: Foundation**
- Day 1-2: Read this overview document
- Day 3-4: Study one detailed architecture doc (start with admin)
- Day 5: Review shared design patterns
**Week 2: Hands-On**
- Day 1-2: Examine existing `dashboard.js` (best example)
- Day 3-4: Copy template and create simple page
- Day 5: Test and understand data flow
**Week 3: Patterns**
- Day 1: Practice base layout inheritance
- Day 2: Implement initialization guards
- Day 3: Use centralized logging
- Day 4: Work with API client
- Day 5: Handle errors properly
**Week 4: Advanced**
- Day 1-2: Create complex page with filters/pagination
- Day 3: Implement modal forms
- Day 4: Add dark mode support
- Day 5: Review and refactor
**After 1 Month:**
- ✅ Understand all three frontends
- ✅ Can create new pages independently
- ✅ Follow all design patterns
- ✅ Debug issues effectively
- ✅ Contribute to architecture improvements
---
## Development Checklist
### Before Creating New Page
- □ Read relevant architecture document
- □ Review page template guide
- □ Study similar existing page
- □ Identify which patterns to use
### While Developing
- □ Use `...data()` for base inheritance
- □ Add initialization guard
- □ Set `currentPage` identifier
- □ Use lowercase `apiClient`
- □ Use centralized logger
- □ Handle errors gracefully
- □ Add loading states
- □ Support dark mode
### Before Committing
- □ No console errors
- □ Initialization guard works
- □ Dark mode works
- □ Mobile responsive
- □ API errors handled
- □ No duplicate API calls
- □ Logging consistent
- □ Code matches patterns
---
## Benefits of This Architecture
### For Developers
- ✅ Copy-paste templates reduce development time
- ✅ Consistent patterns reduce cognitive load
- ✅ Centralized utilities eliminate duplication
- ✅ Clear documentation speeds onboarding
- ✅ Shared patterns enable code reuse
### For The Platform
- ✅ Maintainable codebase
- ✅ Consistent user experience
- ✅ Easier debugging
- ✅ Faster feature development
- ✅ Scalable architecture
### For Users
- ✅ Consistent UI/UX
- ✅ Fast page loads
- ✅ Reliable functionality
- ✅ Professional appearance
- ✅ Works on all devices
---
## Next Steps
1. Read the detailed architecture document for the frontend you're working on (Admin, Vendor, or Shop)
2. Study the page template guide for that frontend
3. Review existing code examples (`dashboard.js` is the best)
4. Check the [Shared Components](shared/ui-components.md) documentation
5. Review the [Icons Guide](../development/icons_guide.md)
6. Copy templates and start building!
Questions? Check the detailed architecture docs or review existing implementations in the codebase.

View File

@@ -393,9 +393,9 @@ window.LogConfig = {
## 📖 More Documentation ## 📖 More Documentation
- [Migration Guide](MIGRATION_GUIDE.md) - [Admin Page Templates](../admin/page-templates.md)
- [Alpine.js Template V2](ALPINE_PAGE_TEMPLATE_V2.md) - [Vendor Page Templates](../vendor/page-templates.md)
- [Recommendation Summary](RECOMMENDATION_SUMMARY.md) - [Shop Page Templates](../shop/page-templates.md)
--- ---

View File

@@ -12,7 +12,7 @@ This guide walks you through setting up the LetzShop database from scratch. Whet
2. [First-Time Setup](#first-time-setup) 2. [First-Time Setup](#first-time-setup)
3. [Database Reset (Clean Slate)](#database-reset-clean-slate) 3. [Database Reset (Clean Slate)](#database-reset-clean-slate)
4. [Verifying Your Setup](#verifying-your-setup) 4. [Verifying Your Setup](#verifying-your-setup)
5. [Common Issues & Solutions](#common-issues--solutions) 5. [Common Issues & Solutions](#common-issues-solutions)
6. [Database Migrations](#database-migrations) 6. [Database Migrations](#database-migrations)
7. [Seeding Data](#seeding-data) 7. [Seeding Data](#seeding-data)

View File

@@ -187,6 +187,6 @@ make migrate-status
## Next Steps ## Next Steps
- [Database Schema Documentation](../development/database-schema.md) - Understand our data model - [Database Setup Guide](DATABASE_SETUP_GUIDE.md) - Complete database setup guide
- [Database Migrations Guide](../development/database-migrations.md) - Advanced migration workflows - [Database Migrations Guide](../development/database-migrations.md) - Advanced migration workflows
- [API Documentation](../api/index.md) - Start building features - [API Documentation](../api/index.md) - Start building features

View File

@@ -1,118 +1,437 @@
# Letzshop Import Documentation # Wizamart Platform Documentation
Welcome to the complete documentation for the Letzshop Import application - a comprehensive marketplace product import and management system built with FastAPI. Welcome to the complete documentation for **Wizamart** - a production-ready, multi-tenant, multi-theme e-commerce platform that enables vendors to operate independent webshops while integrating with external marketplaces.
## What is Letzshop Import? ## What is Wizamart?
Letzshop Import is a powerful web application that enables: Wizamart is a comprehensive multi-tenant e-commerce platform built with **FastAPI** and **Alpine.js**, designed to support multiple vendors with complete data isolation, custom theming, and flexible deployment modes.
- **MarketplaceProduct Management**: Create, update, and manage product catalogs ### Key Capabilities
- **Shop Management**: Multi-shop support with individual configurations
- **CSV Import**: Bulk import products from various marketplace formats - **🏪 Multi-Vendor Marketplace**: Complete vendor isolation with independent webshops
- **Inventory Management**: Track inventory across multiple locations - **🎨 Multi-Theme System**: Vendor-specific branding and customization
- **User Management**: Role-based access control for different user types - **🔗 Marketplace Integration**: Import and curate products from external marketplaces
- **Marketplace Integration**: Import from various marketplace platforms - **📦 Product Catalog Management**: Vendor-scoped product publishing
- **📊 Inventory Management**: Real-time stock tracking with location-based inventory
- **🛒 Shopping Cart**: Session-based cart with real-time Alpine.js updates
- **👥 Customer Management**: Vendor-scoped customer accounts with order history
- **👨‍💼 Team Management**: Role-based access control with granular permissions
- **📱 Order Management**: Complete order lifecycle with status tracking
## Platform Architecture
Wizamart operates on a **multi-tenant architecture** with three distinct interfaces:
```
┌─────────────────────────────────────────────────────────────┐
│ Wizamart Platform │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ ADMIN │ │ VENDOR │ │ SHOP │ │
│ │ Interface │ │ Dashboard │ │ Storefront │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ └──────────────────┴──────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Middleware │ │
│ │ Stack │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ FastAPI │ │
│ │ Backend │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ PostgreSQL │ │
│ │ Database │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Three Deployment Modes
**1. Custom Domain Mode**
```
customdomain.com → Vendor 1 Shop
anothershop.com → Vendor 2 Shop
```
**2. Subdomain Mode**
```
vendor1.platform.com → Vendor 1 Shop
vendor2.platform.com → Vendor 2 Shop
admin.platform.com → Admin Interface
```
**3. Path-Based Mode**
```
platform.com/vendor/vendor1/shop → Vendor 1 Shop
platform.com/vendor/vendor2/shop → Vendor 2 Shop
platform.com/admin → Admin Interface
```
**Learn more**: [Multi-Tenant System](architecture/multi-tenant.md)
## Quick Navigation ## Quick Navigation
### 🚀 Get Started ### 🚀 Getting Started
- [**Installation Guide**](getting-started/installation.md) - Set up the application
- [**Quick Start**](getting-started/quickstart.md) - Get running in minutes
- [**Configuration**](getting-started/configuration.md) - Environment setup
### 📚 API Documentation <div class="grid cards" markdown>
- [**Interactive API Docs**](http://localhost:8000/docs) - Swagger UI for live testing
- [**Alternative API Docs**](http://localhost:8000/redoc) - ReDoc interface - :material-download:{ .lg .middle } __Installation__
- [**API Overview**](api/index.md) - High-level API concepts
- [**Authentication Guide**](api/authentication.md) - Security and auth flows ---
Set up your development environment
[:octicons-arrow-right-24: Installation Guide](getting-started/installation.md)
- :material-rocket-launch:{ .lg .middle } __Quick Start__
---
Get running in 5 minutes
[:octicons-arrow-right-24: Quick Start](getting-started/quickstart.md)
- :material-database:{ .lg .middle } __Database Setup__
---
Initialize database and run migrations
[:octicons-arrow-right-24: Database Setup](getting-started/database-setup.md)
- :material-cog:{ .lg .middle } __Configuration__
---
Environment and settings configuration
[:octicons-arrow-right-24: Configuration](getting-started/configuration.md)
</div>
### 🏛️ Architecture
<div class="grid cards" markdown>
- :material-floor-plan:{ .lg .middle } __System Overview__
---
High-level architecture and design
[:octicons-arrow-right-24: Overview](architecture/overview.md)
- :material-store-multiple:{ .lg .middle } __Multi-Tenant System__
---
Three routing modes explained
[:octicons-arrow-right-24: Multi-Tenancy](architecture/multi-tenant.md)
- :material-layers:{ .lg .middle } __Middleware Stack__
---
Complete middleware documentation
[:octicons-arrow-right-24: Middleware](architecture/middleware.md)
- :material-lock:{ .lg .middle } __Authentication & RBAC__
---
Security and access control
[:octicons-arrow-right-24: Auth & RBAC](architecture/auth-rbac.md)
</div>
### 💻 Development
<div class="grid cards" markdown>
- :material-code-braces:{ .lg .middle } __Backend Development__
---
Services, APIs, and patterns
[:octicons-arrow-right-24: Backend Guide](backend/overview.md)
- :material-api:{ .lg .middle } __API Reference__
---
Technical API documentation
[:octicons-arrow-right-24: API Reference](backend/middleware-reference.md)
- :material-database-cog:{ .lg .middle } __Database Migrations__
---
Alembic migration guide
[:octicons-arrow-right-24: Migrations](development/database-migrations.md)
- :material-electron-framework:{ .lg .middle } __PyCharm Setup__
---
IDE configuration and troubleshooting
[:octicons-arrow-right-24: PyCharm Guide](development/troubleshooting.md)
</div>
### 📚 User Guides
### 📖 User Guides
- [**User Management**](guides/user-management.md) - Managing users and roles - [**User Management**](guides/user-management.md) - Managing users and roles
- [**MarketplaceProduct Management**](guides/product-management.md) - Working with products - [**Product Management**](guides/product-management.md) - Working with products
- [**CSV Import**](guides/csv-import.md) - Bulk import workflows - [**Shop Setup**](guides/shop-setup.md) - Configuring vendor shops
- [**Shop Setup**](guides/shop-setup.md) - Configuring shops - [**CSV Import**](guides/csv-import.md) - Bulk product import
- [**Marketplace Integration**](guides/marketplace-integration.md) - External marketplace setup
### 🧪 Testing ### 🧪 Testing
- [**Testing Guide**](testing/testing-guide.md) - Our testing standards and how to run tests
- [**Test Maintenance**](testing/test-maintenance.md) - Test suite maintenance
### 🔧 Development - [**Testing Guide**](testing/testing-guide.md) - Testing standards and practices
- [**Architecture**](development/architecture.md) - System design overview - [**Test Maintenance**](testing/test-maintenance.md) - Maintaining the test suite
- [**Database Schema**](development/database-schema.md) - Data model documentation
- [**Troubleshooting**](development/troubleshooting.md) - How to troubleshoot
- [**Contributing**](development/contributing.md) - How to contribute
### 🚢 Deployment ### 🚢 Deployment
- [**Docker Deployment**](deployment/docker.md) - Containerized deployment - [**Docker Deployment**](deployment/docker.md) - Containerized deployment
- [**Production Setup**](deployment/production.md) - Production best practices - [**Production Setup**](deployment/production.md) - Production best practices
- [**Environment Variables**](deployment/environment.md) - Configuration reference
## Architecture Overview
```mermaid
graph TB
Client[Web Client/API Consumer]
API[FastAPI Application]
Auth[Authentication Service]
Products[MarketplaceProduct Service]
Shops[Shop Service]
Import[Import Service]
DB[(PostgreSQL Database)]
Client --> API
API --> Auth
API --> Products
API --> Shops
API --> Import
Auth --> DB
Products --> DB
Shops --> DB
Import --> DB
```
## Key Features
=== "MarketplaceProduct Management"
- CRUD operations for products
- GTIN validation and normalization
- Price management with currency support
- Category and attribute management
- Bulk operations support
=== "Import System"
- CSV file processing
- Multiple marketplace format support
- Validation and error reporting
- Batch processing for large files
- Import job tracking and status
=== "Multi-Shop Support"
- Independent shop configurations
- Shop-specific product associations
- Inventory tracking per shop
- Role-based shop access
=== "Security"
- JWT-based authentication
- Role-based access control (RBAC)
- API key management
- Input validation and sanitization
- Rate limiting
## Technology Stack ## Technology Stack
- **Backend**: FastAPI, Python 3.10+ ### Backend
- **Framework**: FastAPI (Python 3.13+)
- **Database**: PostgreSQL with SQLAlchemy ORM - **Database**: PostgreSQL with SQLAlchemy ORM
- **Authentication**: JWT tokens - **Authentication**: JWT tokens with bcrypt
- **Testing**: pytest with comprehensive test suite - **Async**: Uvicorn ASGI server
- **Documentation**: MkDocs Material + FastAPI auto-docs - **Migrations**: Alembic
- **Deployment**: Docker, Docker Compose - **Testing**: pytest with comprehensive coverage
### Frontend
- **Templates**: Jinja2 server-side rendering
- **JavaScript**: Alpine.js for reactive components (CDN-based, no build step)
- **CSS**: Tailwind CSS with custom themes
- **Icons**: Lucide Icons
- **Zero Build Step**: Works directly without compilation
### Infrastructure
- **Web Server**: Uvicorn (ASGI)
- **Middleware**: Custom stack for multi-tenancy
- **Static Files**: FastAPI StaticFiles
- **Documentation**: MkDocs Material + mkdocstrings
## Key Features
### Multi-Tenancy
- **Complete Data Isolation**: Chinese wall between vendor data
- **Flexible Routing**: Custom domains, subdomains, or path-based
- **Vendor Context Detection**: Automatic tenant identification
- **Scoped Queries**: All database queries vendor-scoped by default
**Learn more**: [Multi-Tenant System](architecture/multi-tenant.md)
### Multi-Theme Support
- **Vendor Branding**: Custom colors, logos, and styling
- **CSS Variables**: Dynamic theming with CSS custom properties
- **Theme Inheritance**: Base themes with vendor overrides
- **Real-time Updates**: Theme changes apply instantly
### Security & Authentication
- **JWT-based Authentication**: Secure token-based auth
- **Role-Based Access Control**: Admin, Vendor, Customer roles
- **Team Permissions**: Granular permissions for vendor team members
- **Input Validation**: Pydantic models for all requests
- **Rate Limiting**: API protection with sliding window algorithm
**Learn more**: [Authentication & RBAC](architecture/auth-rbac.md)
### Alpine.js Reactive UI
- **Lightweight**: 15KB framework for reactive components
- **No Build Step**: Works directly in HTML
- **Reactive State**: Modern UX without complexity
- **Perfect Integration**: Seamless with Jinja2 templates
- **Scoped Components**: Natural vendor isolation
### Service Layer Architecture
- **Clean Separation**: Business logic isolated from routes
- **Reusable Services**: Shared across API and page routes
- **Exception-First**: Consistent error handling
- **Testable**: Easy unit and integration testing
**Learn more**: [Backend Development](backend/overview.md)
## Request Flow
Understanding how a request flows through Wizamart:
```mermaid
graph LR
A[Client Request] --> B[LoggingMiddleware]
B --> C[VendorContextMiddleware]
C --> D[PathRewriteMiddleware]
D --> E[ContextDetectionMiddleware]
E --> F[ThemeContextMiddleware]
F --> G[FastAPI Router]
G --> H{Request Type}
H -->|API| I[JSON Response]
H -->|Page| J[HTML Template]
```
**Learn more**: [Request Flow](architecture/request-flow.md)
## Live Documentation
When running the application locally:
- **Swagger UI**: [http://localhost:8000/docs](http://localhost:8000/docs) - Interactive API testing
- **ReDoc**: [http://localhost:8000/redoc](http://localhost:8000/redoc) - Alternative API docs
- **Admin Panel**: [http://localhost:8000/admin](http://localhost:8000/admin) - Platform administration
- **Health Check**: [http://localhost:8000/health](http://localhost:8000/health) - System status
## First Steps
### For Developers
1. **Set up your environment**
- [Installation Guide](getting-started/installation.md)
- [Database Setup](getting-started/database-setup.md)
- [Configuration](getting-started/configuration.md)
2. **Understand the architecture**
- [System Overview](architecture/overview.md)
- [Multi-Tenant System](architecture/multi-tenant.md)
- [Middleware Stack](architecture/middleware.md)
3. **Start developing**
- [Backend Development](backend/overview.md)
- [Database Migrations](development/database-migrations.md)
- [Testing Guide](testing/testing-guide.md)
### For Operations
1. **Deploy the platform**
- [Docker Deployment](deployment/docker.md)
- [Production Setup](deployment/production.md)
- [Environment Variables](deployment/environment.md)
2. **Configure and manage**
- [Configuration Guide](getting-started/configuration.md)
- [Database Setup](getting-started/database-setup.md)
### For Team Members
1. **Get your IDE ready**
- [PyCharm Setup](development/pycharm-configuration-make.md)
- [Troubleshooting Guide](development/troubleshooting.md)
2. **Learn the workflow**
- [Contributing Guide](development/contributing.md)
- [Testing Standards](testing/testing-guide.md)
## Development Commands
Quick reference for common development tasks:
```bash
# Setup
make install-all # Install all dependencies
make setup # Complete setup (install + migrate + seed)
# Development
make dev # Start development server
make docs-serve # Start documentation server
# Database
make migrate-up # Run migrations
make seed-demo # Seed demo data
make db-setup # Complete database setup
# Testing
make test # Run all tests
make test-coverage # Run with coverage report
# Code Quality
make format # Format code (black + isort)
make lint # Run linters
make check # Format + lint
# Documentation
make docs-build # Build documentation
make docs-serve # Serve documentation locally
```
## Documentation Structure
This documentation is organized into logical sections:
- **Getting Started**: Installation, configuration, and setup
- **Architecture**: System design and multi-tenancy
- **Backend Development**: Services, APIs, and patterns
- **API Documentation**: For API consumers
- **User Guides**: Feature-specific guides
- **Testing**: Testing practices and standards
- **Development**: Tools, workflows, and troubleshooting
- **Deployment**: Production deployment guides
## Getting Help ## Getting Help
- **Issues**: [GitHub Issues](https://github.com/yourusername/letzshop-import/issues) - **GitHub Issues**: Report bugs and request features
- **Discussions**: [GitHub Discussions](https://github.com/yourusername/letzshop-import/discussions) - **API Documentation**: Interactive Swagger UI at `/docs`
- **API Testing**: Use the [Swagger UI](http://localhost:8000/docs) for interactive testing - **Architecture Docs**: [System Overview](architecture/overview.md)
- **Troubleshooting**: [PyCharm Troubleshooting](development/troubleshooting.md)
## Contributing
We welcome contributions! Please see our [Contributing Guide](development/contributing.md) for:
- Development workflow
- Code quality standards
- Testing requirements
- Pull request process
## Project Status
**Current Version**: Production-ready multi-tenant platform
**Completed Features**:
- ✅ Multi-tenant architecture with 3 routing modes
- ✅ Complete vendor data isolation
- ✅ Multi-theme system
- ✅ JWT authentication & RBAC
- ✅ Product catalog management
- ✅ Order processing
- ✅ Shopping cart with Alpine.js
- ✅ Marketplace integration
- ✅ Team management
**In Development**:
- 🚧 Payment integration (Stripe-ready)
- 🚧 Email notifications
- 🚧 Advanced analytics
--- ---
Ready to get started? Head over to the [Installation Guide](getting-started/installation.md)! **Ready to get started?** Head over to the [Installation Guide](getting-started/installation.md) or explore the [Architecture Overview](architecture/overview.md)!
Built with ❤️ using FastAPI, PostgreSQL, Alpine.js, and modern Python patterns for a scalable, maintainable multi-tenant e-commerce platform.

View File

@@ -1,16 +1,24 @@
# middleware/auth.py # middleware/auth.py
"""Summary description .... """Authentication and Authorization Module.
This module provides classes and functions for: This module provides JWT-based authentication and role-based access control (RBAC)
- .... for the application. It handles:
- .... - Password hashing and verification using bcrypt
- .... - JWT token creation and validation
- User authentication against the database
- Role-based access control (admin, vendor, customer)
- Current user extraction from request credentials
The module uses the following technologies:
- Jose for JWT token handling
- Passlib with bcrypt for secure password hashing
- SQLAlchemy for database operations
""" """
import logging import logging
import os import os
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional from typing import Any, Callable, Dict, Optional
from fastapi import HTTPException from fastapi import HTTPException
from fastapi.security import HTTPAuthorizationCredentials from fastapi.security import HTTPAuthorizationCredentials
@@ -34,132 +42,282 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class AuthManager: class AuthManager:
"""JWT-based authentication manager with bcrypt password hashing.""" """JWT-based authentication manager with bcrypt password hashing.
This class provides a complete authentication system including:
- User credential verification
- JWT token generation and validation
- Role-based access control
- Password hashing using bcrypt
Attributes:
secret_key (str): Secret key used for JWT encoding/decoding
algorithm (str): Algorithm used for JWT signing (HS256)
token_expire_minutes (int): Token expiration time in minutes
"""
def __init__(self): def __init__(self):
"""Class constructor.""" """Initialize the AuthManager with configuration from environment variables.
Reads the following environment variables:
- JWT_SECRET_KEY: Secret key for JWT signing (defaults to development key)
- JWT_EXPIRE_MINUTES: Token expiration time in minutes (defaults to 30)
"""
# Load JWT secret key from environment, with fallback for development
self.secret_key = os.getenv( self.secret_key = os.getenv(
"JWT_SECRET_KEY", "your-secret-key-change-in-production-please" "JWT_SECRET_KEY", "your-secret-key-change-in-production-please"
) )
# Use HS256 (HMAC with SHA-256) for token signing
self.algorithm = "HS256" self.algorithm = "HS256"
# Configure token expiration time from environment
self.token_expire_minutes = int(os.getenv("JWT_EXPIRE_MINUTES", "30")) self.token_expire_minutes = int(os.getenv("JWT_EXPIRE_MINUTES", "30"))
def hash_password(self, password: str) -> str: def hash_password(self, password: str) -> str:
"""Hash password using bcrypt.""" """Hash a plain text password using bcrypt.
Uses bcrypt hashing algorithm with automatic salt generation.
The resulting hash is safe to store in the database.
Args:
password (str): Plain text password to hash
Returns:
str: Bcrypt hashed password string
"""
return pwd_context.hash(password) return pwd_context.hash(password)
def verify_password(self, plain_password: str, hashed_password: str) -> bool: def verify_password(self, plain_password: str, hashed_password: str) -> bool:
"""Verify password against hash.""" """Verify a plain text password against a bcrypt hash.
Args:
plain_password (str): Plain text password to verify
hashed_password (str): Bcrypt hash to verify against
Returns:
bool: True if password matches hash, False otherwise
"""
return pwd_context.verify(plain_password, hashed_password) return pwd_context.verify(plain_password, hashed_password)
def authenticate_user(self, db: Session, username: str, password: str) -> Optional[User]: def authenticate_user(self, db: Session, username: str, password: str) -> Optional[User]:
"""Authenticate user and return user object if credentials are valid.""" """Authenticate user credentials against the database.
Supports authentication using either username or email address.
Verifies the provided password against the stored bcrypt hash.
Args:
db (Session): SQLAlchemy database session
username (str): Username or email address to authenticate
password (str): Plain text password to verify
Returns:
Optional[User]: User object if authentication succeeds, None otherwise
"""
# Query user by username or email (both are unique identifiers)
user = ( user = (
db.query(User) db.query(User)
.filter((User.username == username) | (User.email == username)) .filter((User.username == username) | (User.email == username))
.first() .first()
) )
# User not found in database
if not user: if not user:
return None return None
# Password verification failed
if not self.verify_password(password, user.hashed_password): if not self.verify_password(password, user.hashed_password):
return None return None
return user # Return user if credentials are valid # Authentication successful, return user object
return user
def create_access_token(self, user: User) -> Dict[str, Any]: def create_access_token(self, user: User) -> Dict[str, Any]:
"""Create JWT access token for user.""" """Create a JWT access token for an authenticated user.
The token includes user identity and role information in the payload.
Token expiration is set based on the configured token_expire_minutes.
Args:
user (User): Authenticated user object
Returns:
Dict[str, Any]: Dictionary containing:
- access_token (str): The JWT token string
- token_type (str): Token type ("bearer")
- expires_in (int): Token lifetime in seconds
"""
# Calculate token expiration time
expires_delta = timedelta(minutes=self.token_expire_minutes) expires_delta = timedelta(minutes=self.token_expire_minutes)
expire = datetime.now(timezone.utc) + expires_delta expire = datetime.now(timezone.utc) + expires_delta
# Build JWT payload with user information
payload = { payload = {
"sub": str(user.id), "sub": str(user.id), # Subject: user ID (JWT standard claim)
"username": user.username, "username": user.username, # Username for display/logging
"email": user.email, "email": user.email, # User email address
"role": user.role, "role": user.role, # User role for authorization
"exp": expire, "exp": expire, # Expiration time (JWT standard claim)
"iat": datetime.now(timezone.utc), "iat": datetime.now(timezone.utc), # Issued at time (JWT standard claim)
} }
# Encode the payload into a JWT token
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm) token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
# Return token with metadata
return { return {
"access_token": token, "access_token": token,
"token_type": "bearer", "token_type": "bearer",
"expires_in": self.token_expire_minutes * 60, # Return in seconds "expires_in": self.token_expire_minutes * 60, # Convert minutes to seconds
} }
def verify_token(self, token: str) -> Dict[str, Any]: def verify_token(self, token: str) -> Dict[str, Any]:
"""Verify JWT token and return user data.""" """Verify and decode a JWT token, returning the user data.
Validates the token signature, expiration, and required claims.
Ensures the token contains all necessary user identification data.
Args:
token (str): JWT token string to verify
Returns:
Dict[str, Any]: Dictionary containing:
- user_id (int): User's database ID
- username (str): User's username
- email (str): User's email address
- role (str): User's role (defaults to "user" if not present)
Raises:
TokenExpiredException: If token has expired
InvalidTokenException: If token is malformed, missing required claims,
or signature verification fails
"""
try: try:
# Decode and verify the JWT token signature
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm]) payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
# Check if token has expired # Validate token expiration claim exists
exp = payload.get("exp") exp = payload.get("exp")
if exp is None: if exp is None:
raise InvalidTokenException("Token missing expiration") raise InvalidTokenException("Token missing expiration")
# Check if token has expired (additional check beyond jwt.decode)
if datetime.now(timezone.utc) > datetime.fromtimestamp(exp, tz=timezone.utc): if datetime.now(timezone.utc) > datetime.fromtimestamp(exp, tz=timezone.utc):
raise TokenExpiredException() raise TokenExpiredException()
# Extract user data # Validate user identifier claim exists
user_id = payload.get("sub") user_id = payload.get("sub")
if user_id is None: if user_id is None:
raise InvalidTokenException("Token missing user identifier") raise InvalidTokenException("Token missing user identifier")
# Extract and return user data from token payload
return { return {
"user_id": int(user_id), "user_id": int(user_id),
"username": payload.get("username"), "username": payload.get("username"),
"email": payload.get("email"), "email": payload.get("email"),
"role": payload.get("role", "user"), "role": payload.get("role", "user"), # Default to "user" role if not specified
} }
except jwt.ExpiredSignatureError: except jwt.ExpiredSignatureError:
# Token has expired (caught by jwt.decode)
raise TokenExpiredException() raise TokenExpiredException()
except jwt.JWTError as e: except jwt.JWTError as e:
# Token signature verification failed or token is malformed
logger.error(f"JWT decode error: {e}") logger.error(f"JWT decode error: {e}")
raise InvalidTokenException("Could not validate credentials") raise InvalidTokenException("Could not validate credentials")
except Exception as e: except Exception as e:
# Catch any other unexpected errors during token verification
logger.error(f"Token verification error: {e}") logger.error(f"Token verification error: {e}")
raise InvalidTokenException("Authentication failed") raise InvalidTokenException("Authentication failed")
def get_current_user(self, db: Session, credentials: HTTPAuthorizationCredentials) -> User: def get_current_user(self, db: Session, credentials: HTTPAuthorizationCredentials) -> User:
"""Get current authenticated user from database.""" """Extract and validate the current authenticated user from request credentials.
Verifies the JWT token from the Authorization header, looks up the user
in the database, and ensures the user account is active.
Args:
db (Session): SQLAlchemy database session
credentials (HTTPAuthorizationCredentials): Bearer token credentials from request
Returns:
User: The authenticated and active user object
Raises:
InvalidTokenException: If token verification fails
InvalidCredentialsException: If user is not found in database
UserNotActiveException: If user account is inactive
"""
# Verify JWT token and extract user data
user_data = self.verify_token(credentials.credentials) user_data = self.verify_token(credentials.credentials)
# Look up user in database by ID from token
user = db.query(User).filter(User.id == user_data["user_id"]).first() user = db.query(User).filter(User.id == user_data["user_id"]).first()
if not user: if not user:
raise InvalidCredentialsException("User not found") raise InvalidCredentialsException("User not found")
# Ensure user account is active
if not user.is_active: if not user.is_active:
raise UserNotActiveException() raise UserNotActiveException()
return user return user
def require_role(self, required_role: str): def require_role(self, required_role: str) -> Callable:
"""Require specific role.""" """Create a decorator that enforces a specific role requirement.
This method returns a decorator that can be used to protect functions
requiring a specific user role. The decorator validates that the current
user has the exact required role.
Args:
required_role (str): The role name required (e.g., "admin", "vendor")
Returns:
Callable: Decorator function that enforces role requirement
Raises:
HTTPException: If user doesn't have the required role (403 Forbidden)
Example:
@auth_manager.require_role("admin")
def admin_only_function(current_user: User):
# This will only execute if user has "admin" role
pass
"""
def decorator(func): def decorator(func):
"""Decorator that wraps the function with role checking."""
def wrapper(current_user: User, *args, **kwargs): def wrapper(current_user: User, *args, **kwargs):
# Check if current user has the required role
if current_user.role != required_role: if current_user.role != required_role:
raise HTTPException( raise HTTPException(
status_code=403, status_code=403,
detail=f"Required role '{required_role}' not found. Current role: '{current_user.role}'", detail=f"Required role '{required_role}' not found. Current role: '{current_user.role}'",
) )
# User has required role, proceed with function execution
return func(current_user, *args, **kwargs) return func(current_user, *args, **kwargs)
return wrapper return wrapper
return decorator return decorator
def require_admin(self, current_user: User): def require_admin(self, current_user: User) -> User:
"""Require admin role.""" """Enforce that the current user has admin role.
Use this as a dependency in FastAPI routes to restrict access to admins only.
Args:
current_user (User): The authenticated user from get_current_user
Returns:
User: The user object if they have admin role
Raises:
AdminRequiredException: If user does not have admin role
"""
# Verify user has admin role
if current_user.role != "admin": if current_user.role != "admin":
raise AdminRequiredException() raise AdminRequiredException()
return current_user return current_user
def require_vendor(self, current_user: User): def require_vendor(self, current_user: User) -> User:
""" """
Require vendor role (vendor or admin). Require vendor role (vendor or admin).
@@ -174,6 +332,7 @@ class AuthManager:
Raises: Raises:
InsufficientPermissionsException: If user is not vendor or admin InsufficientPermissionsException: If user is not vendor or admin
""" """
# Check if user has vendor or admin role (admins have full access)
if current_user.role not in ["vendor", "admin"]: if current_user.role not in ["vendor", "admin"]:
from app.exceptions import InsufficientPermissionsException from app.exceptions import InsufficientPermissionsException
raise InsufficientPermissionsException( raise InsufficientPermissionsException(
@@ -182,7 +341,7 @@ class AuthManager:
) )
return current_user return current_user
def require_customer(self, current_user: User): def require_customer(self, current_user: User) -> User:
""" """
Require customer role (customer or admin). Require customer role (customer or admin).
@@ -197,6 +356,7 @@ class AuthManager:
Raises: Raises:
InsufficientPermissionsException: If user is not customer or admin InsufficientPermissionsException: If user is not customer or admin
""" """
# Check if user has customer or admin role (admins have full access)
if current_user.role not in ["customer", "admin"]: if current_user.role not in ["customer", "admin"]:
from app.exceptions import InsufficientPermissionsException from app.exceptions import InsufficientPermissionsException
raise InsufficientPermissionsException( raise InsufficientPermissionsException(
@@ -205,12 +365,32 @@ class AuthManager:
) )
return current_user return current_user
def create_default_admin_user(self, db: Session): def create_default_admin_user(self, db: Session) -> User:
"""Create default admin user if it doesn't exist.""" """Create a default admin user account if one doesn't already exist.
This is typically used during initial application setup or database seeding.
The default admin account should have its password changed immediately in production.
Default credentials:
- Username: admin
- Password: admin123 (CHANGE IN PRODUCTION!)
- Email: admin@example.com
Args:
db (Session): SQLAlchemy database session
Returns:
User: The admin user object (either existing or newly created)
"""
# Check if admin user already exists
admin_user = db.query(User).filter(User.username == "admin").first() admin_user = db.query(User).filter(User.username == "admin").first()
# Create admin user if it doesn't exist
if not admin_user: if not admin_user:
# Hash the default password securely
hashed_password = self.hash_password("admin123") hashed_password = self.hash_password("admin123")
# Create new admin user with default credentials
admin_user = User( admin_user = User(
email="admin@example.com", email="admin@example.com",
username="admin", username="admin",
@@ -218,9 +398,13 @@ class AuthManager:
role="admin", role="admin",
is_active=True, is_active=True,
) )
# Save to database
db.add(admin_user) db.add(admin_user)
db.commit() db.commit()
db.refresh(admin_user) db.refresh(admin_user)
# Log creation for audit trail (WARNING: contains default credentials)
logger.info( logger.info(
"Default admin user created: username='admin', password='admin123'" "Default admin user created: username='admin', password='admin123'"
) )

Some files were not shown because too many files have changed in this diff Show More