compiling project documentation
This commit is contained in:
@@ -1,617 +0,0 @@
|
||||
# Stripe Payment Integration - Multi-Tenant Ecommerce Platform
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The payment integration uses **Stripe Connect** to handle multi-vendor payments, enabling:
|
||||
- Each vendor to receive payments directly
|
||||
- Platform to collect fees/commissions
|
||||
- Proper financial isolation between vendors
|
||||
- Compliance with financial regulations
|
||||
|
||||
## Payment Models
|
||||
|
||||
### Database Models
|
||||
|
||||
```python
|
||||
# models/database/payment.py
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, Numeric
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.core.database import Base
|
||||
from .base import TimestampMixin
|
||||
|
||||
|
||||
class VendorPaymentConfig(Base, TimestampMixin):
|
||||
"""Vendor-specific payment configuration."""
|
||||
__tablename__ = "vendor_payment_configs"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False, unique=True)
|
||||
|
||||
# Stripe Connect configuration
|
||||
stripe_account_id = Column(String(255)) # Stripe Connect account ID
|
||||
stripe_account_status = Column(String(50)) # pending, active, restricted, inactive
|
||||
stripe_onboarding_url = Column(Text) # Onboarding link for vendor
|
||||
stripe_dashboard_url = Column(Text) # Vendor's Stripe dashboard
|
||||
|
||||
# Payment settings
|
||||
accepts_payments = Column(Boolean, default=False)
|
||||
currency = Column(String(3), default="EUR")
|
||||
platform_fee_percentage = Column(Numeric(5, 2), default=2.5) # Platform commission
|
||||
|
||||
# Payout settings
|
||||
payout_schedule = Column(String(20), default="weekly") # daily, weekly, monthly
|
||||
minimum_payout = Column(Numeric(10, 2), default=20.00)
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor", back_populates="payment_config")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<VendorPaymentConfig(vendor_id={self.vendor_id}, stripe_account_id='{self.stripe_account_id}')>"
|
||||
|
||||
|
||||
class Payment(Base, TimestampMixin):
|
||||
"""Payment records for orders."""
|
||||
__tablename__ = "payments"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
|
||||
order_id = Column(Integer, ForeignKey("orders.id"), nullable=False)
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
|
||||
|
||||
# Stripe payment details
|
||||
stripe_payment_intent_id = Column(String(255), unique=True, index=True)
|
||||
stripe_charge_id = Column(String(255), index=True)
|
||||
stripe_transfer_id = Column(String(255)) # Transfer to vendor account
|
||||
|
||||
# Payment amounts (in cents to avoid floating point issues)
|
||||
amount_total = Column(Integer, nullable=False) # Total customer payment
|
||||
amount_vendor = Column(Integer, nullable=False) # Amount to vendor
|
||||
amount_platform_fee = Column(Integer, nullable=False) # Platform commission
|
||||
currency = Column(String(3), default="EUR")
|
||||
|
||||
# Payment status
|
||||
status = Column(String(50), nullable=False) # pending, succeeded, failed, refunded
|
||||
payment_method = Column(String(50)) # card, bank_transfer, etc.
|
||||
|
||||
# Metadata
|
||||
stripe_metadata = Column(Text) # JSON string of Stripe metadata
|
||||
failure_reason = Column(Text)
|
||||
refund_reason = Column(Text)
|
||||
|
||||
# Timestamps
|
||||
paid_at = Column(DateTime)
|
||||
refunded_at = Column(DateTime)
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor")
|
||||
order = relationship("Order", back_populates="payment")
|
||||
customer = relationship("Customer")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Payment(id={self.id}, order_id={self.order_id}, status='{self.status}')>"
|
||||
|
||||
@property
|
||||
def amount_total_euros(self):
|
||||
"""Convert cents to euros for display."""
|
||||
return self.amount_total / 100
|
||||
|
||||
@property
|
||||
def amount_vendor_euros(self):
|
||||
"""Convert cents to euros for display."""
|
||||
return self.amount_vendor / 100
|
||||
|
||||
|
||||
class PaymentMethod(Base, TimestampMixin):
|
||||
"""Saved customer payment methods."""
|
||||
__tablename__ = "payment_methods"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
|
||||
|
||||
# Stripe payment method details
|
||||
stripe_payment_method_id = Column(String(255), nullable=False, index=True)
|
||||
payment_method_type = Column(String(50), nullable=False) # card, sepa_debit, etc.
|
||||
|
||||
# Card details (if applicable)
|
||||
card_brand = Column(String(50)) # visa, mastercard, etc.
|
||||
card_last4 = Column(String(4))
|
||||
card_exp_month = Column(Integer)
|
||||
card_exp_year = Column(Integer)
|
||||
|
||||
# Settings
|
||||
is_default = Column(Boolean, default=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor")
|
||||
customer = relationship("Customer")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PaymentMethod(id={self.id}, customer_id={self.customer_id}, type='{self.payment_method_type}')>"
|
||||
```
|
||||
|
||||
### Updated Order Model
|
||||
|
||||
```python
|
||||
# Update models/database/order.py
|
||||
class Order(Base, TimestampMixin):
|
||||
# ... existing fields ...
|
||||
|
||||
# Payment integration
|
||||
payment_status = Column(String(50), default="pending") # pending, paid, failed, refunded
|
||||
payment_intent_id = Column(String(255)) # Stripe PaymentIntent ID
|
||||
total_amount_cents = Column(Integer, nullable=False) # Amount in cents
|
||||
|
||||
# Relationships
|
||||
payment = relationship("Payment", back_populates="order", uselist=False)
|
||||
|
||||
@property
|
||||
def total_amount_euros(self):
|
||||
"""Convert cents to euros for display."""
|
||||
return self.total_amount_cents / 100 if self.total_amount_cents else 0
|
||||
```
|
||||
|
||||
## Payment Service Integration
|
||||
|
||||
### Stripe Service
|
||||
|
||||
```python
|
||||
# services/payment_service.py
|
||||
import stripe
|
||||
import json
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from typing import Dict, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import settings
|
||||
from models.database.payment import Payment, VendorPaymentConfig
|
||||
from models.database.order import Order
|
||||
from models.database.vendor import Vendor
|
||||
from app.exceptions.payment import *
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configure Stripe
|
||||
stripe.api_key = settings.stripe_secret_key
|
||||
|
||||
|
||||
class PaymentService:
|
||||
"""Service for handling Stripe payments in multi-tenant environment."""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def create_payment_intent(
|
||||
self,
|
||||
vendor_id: int,
|
||||
order_id: int,
|
||||
amount_euros: Decimal,
|
||||
customer_email: str,
|
||||
metadata: Optional[Dict] = None
|
||||
) -> Dict:
|
||||
"""Create Stripe PaymentIntent for vendor order."""
|
||||
|
||||
# Get vendor payment configuration
|
||||
payment_config = self.get_vendor_payment_config(vendor_id)
|
||||
if not payment_config.accepts_payments:
|
||||
raise PaymentNotConfiguredException(f"Vendor {vendor_id} not configured for payments")
|
||||
|
||||
# Calculate amounts
|
||||
amount_cents = int(amount_euros * 100)
|
||||
platform_fee_cents = int(amount_cents * (payment_config.platform_fee_percentage / 100))
|
||||
vendor_amount_cents = amount_cents - platform_fee_cents
|
||||
|
||||
try:
|
||||
# Create PaymentIntent with Stripe Connect
|
||||
payment_intent = stripe.PaymentIntent.create(
|
||||
amount=amount_cents,
|
||||
currency=payment_config.currency.lower(),
|
||||
application_fee_amount=platform_fee_cents,
|
||||
transfer_data={
|
||||
'destination': payment_config.stripe_account_id,
|
||||
},
|
||||
metadata={
|
||||
'vendor_id': str(vendor_id),
|
||||
'order_id': str(order_id),
|
||||
'platform': 'multi_tenant_ecommerce',
|
||||
**(metadata or {})
|
||||
},
|
||||
receipt_email=customer_email,
|
||||
description=f"Order payment for vendor {vendor_id}"
|
||||
)
|
||||
|
||||
# Create payment record
|
||||
payment = Payment(
|
||||
vendor_id=vendor_id,
|
||||
order_id=order_id,
|
||||
customer_id=self.get_order_customer_id(order_id),
|
||||
stripe_payment_intent_id=payment_intent.id,
|
||||
amount_total=amount_cents,
|
||||
amount_vendor=vendor_amount_cents,
|
||||
amount_platform_fee=platform_fee_cents,
|
||||
currency=payment_config.currency,
|
||||
status='pending',
|
||||
stripe_metadata=json.dumps(payment_intent.metadata)
|
||||
)
|
||||
|
||||
self.db.add(payment)
|
||||
|
||||
# Update order
|
||||
order = self.db.query(Order).filter(Order.id == order_id).first()
|
||||
if order:
|
||||
order.payment_intent_id = payment_intent.id
|
||||
order.payment_status = 'pending'
|
||||
|
||||
self.db.commit()
|
||||
|
||||
return {
|
||||
'payment_intent_id': payment_intent.id,
|
||||
'client_secret': payment_intent.client_secret,
|
||||
'amount_total': amount_euros,
|
||||
'amount_vendor': vendor_amount_cents / 100,
|
||||
'platform_fee': platform_fee_cents / 100,
|
||||
'currency': payment_config.currency
|
||||
}
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"Stripe error creating PaymentIntent: {e}")
|
||||
raise PaymentProcessingException(f"Payment processing failed: {str(e)}")
|
||||
|
||||
def confirm_payment(self, payment_intent_id: str) -> Payment:
|
||||
"""Confirm payment and update records."""
|
||||
|
||||
try:
|
||||
# Retrieve PaymentIntent from Stripe
|
||||
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
|
||||
|
||||
# Find payment record
|
||||
payment = self.db.query(Payment).filter(
|
||||
Payment.stripe_payment_intent_id == payment_intent_id
|
||||
).first()
|
||||
|
||||
if not payment:
|
||||
raise PaymentNotFoundException(f"Payment not found for intent {payment_intent_id}")
|
||||
|
||||
# Update payment status based on Stripe status
|
||||
if payment_intent.status == 'succeeded':
|
||||
payment.status = 'succeeded'
|
||||
payment.stripe_charge_id = payment_intent.charges.data[0].id if payment_intent.charges.data else None
|
||||
payment.paid_at = datetime.utcnow()
|
||||
|
||||
# Update order status
|
||||
order = self.db.query(Order).filter(Order.id == payment.order_id).first()
|
||||
if order:
|
||||
order.payment_status = 'paid'
|
||||
order.status = 'processing' # Move order to processing
|
||||
|
||||
elif payment_intent.status == 'payment_failed':
|
||||
payment.status = 'failed'
|
||||
payment.failure_reason = payment_intent.last_payment_error.message if payment_intent.last_payment_error else "Unknown error"
|
||||
|
||||
# Update order status
|
||||
order = self.db.query(Order).filter(Order.id == payment.order_id).first()
|
||||
if order:
|
||||
order.payment_status = 'failed'
|
||||
|
||||
self.db.commit()
|
||||
|
||||
return payment
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"Stripe error confirming payment: {e}")
|
||||
raise PaymentProcessingException(f"Payment confirmation failed: {str(e)}")
|
||||
|
||||
def create_vendor_stripe_account(self, vendor_id: int, vendor_data: Dict) -> str:
|
||||
"""Create Stripe Connect account for vendor."""
|
||||
|
||||
try:
|
||||
# Create Stripe Connect Express account
|
||||
account = stripe.Account.create(
|
||||
type='express',
|
||||
country='LU', # Luxembourg
|
||||
email=vendor_data.get('business_email'),
|
||||
capabilities={
|
||||
'card_payments': {'requested': True},
|
||||
'transfers': {'requested': True},
|
||||
},
|
||||
business_type='company',
|
||||
company={
|
||||
'name': vendor_data.get('business_name'),
|
||||
'phone': vendor_data.get('business_phone'),
|
||||
'address': {
|
||||
'line1': vendor_data.get('address_line1'),
|
||||
'city': vendor_data.get('city'),
|
||||
'postal_code': vendor_data.get('postal_code'),
|
||||
'country': 'LU'
|
||||
}
|
||||
},
|
||||
metadata={
|
||||
'vendor_id': str(vendor_id),
|
||||
'platform': 'multi_tenant_ecommerce'
|
||||
}
|
||||
)
|
||||
|
||||
# Update or create payment configuration
|
||||
payment_config = self.get_or_create_vendor_payment_config(vendor_id)
|
||||
payment_config.stripe_account_id = account.id
|
||||
payment_config.stripe_account_status = account.charges_enabled and account.payouts_enabled and 'active' or 'pending'
|
||||
|
||||
self.db.commit()
|
||||
|
||||
return account.id
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"Stripe error creating account: {e}")
|
||||
raise PaymentConfigurationException(f"Failed to create payment account: {str(e)}")
|
||||
|
||||
def create_onboarding_link(self, vendor_id: int) -> str:
|
||||
"""Create Stripe onboarding link for vendor."""
|
||||
|
||||
payment_config = self.get_vendor_payment_config(vendor_id)
|
||||
if not payment_config.stripe_account_id:
|
||||
raise PaymentNotConfiguredException("Vendor does not have Stripe account")
|
||||
|
||||
try:
|
||||
account_link = stripe.AccountLink.create(
|
||||
account=payment_config.stripe_account_id,
|
||||
refresh_url=f"{settings.frontend_url}/vendor/admin/payments/refresh",
|
||||
return_url=f"{settings.frontend_url}/vendor/admin/payments/success",
|
||||
type='account_onboarding',
|
||||
)
|
||||
|
||||
# Update onboarding URL
|
||||
payment_config.stripe_onboarding_url = account_link.url
|
||||
self.db.commit()
|
||||
|
||||
return account_link.url
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"Stripe error creating onboarding link: {e}")
|
||||
raise PaymentConfigurationException(f"Failed to create onboarding link: {str(e)}")
|
||||
|
||||
def get_vendor_payment_config(self, vendor_id: int) -> VendorPaymentConfig:
|
||||
"""Get vendor payment configuration."""
|
||||
config = self.db.query(VendorPaymentConfig).filter(
|
||||
VendorPaymentConfig.vendor_id == vendor_id
|
||||
).first()
|
||||
|
||||
if not config:
|
||||
raise PaymentNotConfiguredException(f"No payment configuration for vendor {vendor_id}")
|
||||
|
||||
return config
|
||||
|
||||
def webhook_handler(self, event_type: str, event_data: Dict) -> None:
|
||||
"""Handle Stripe webhook events."""
|
||||
|
||||
if event_type == 'payment_intent.succeeded':
|
||||
payment_intent_id = event_data['object']['id']
|
||||
self.confirm_payment(payment_intent_id)
|
||||
|
||||
elif event_type == 'payment_intent.payment_failed':
|
||||
payment_intent_id = event_data['object']['id']
|
||||
self.confirm_payment(payment_intent_id)
|
||||
|
||||
elif event_type == 'account.updated':
|
||||
# Update vendor account status
|
||||
account_id = event_data['object']['id']
|
||||
self.update_vendor_account_status(account_id, event_data['object'])
|
||||
|
||||
# Add more webhook handlers as needed
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Payment APIs
|
||||
|
||||
```python
|
||||
# app/api/v1/vendor/payments.py
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from middleware.vendor_context import require_vendor_context
|
||||
from models.database.vendor import Vendor
|
||||
from services.payment_service import PaymentService
|
||||
|
||||
router = APIRouter(prefix="/payments", tags=["vendor-payments"])
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
async def get_payment_config(
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get vendor payment configuration."""
|
||||
payment_service = PaymentService(db)
|
||||
|
||||
try:
|
||||
config = payment_service.get_vendor_payment_config(vendor.id)
|
||||
return {
|
||||
"stripe_account_id": config.stripe_account_id,
|
||||
"account_status": config.stripe_account_status,
|
||||
"accepts_payments": config.accepts_payments,
|
||||
"currency": config.currency,
|
||||
"platform_fee_percentage": float(config.platform_fee_percentage),
|
||||
"needs_onboarding": config.stripe_account_status != 'active'
|
||||
}
|
||||
except Exception:
|
||||
return {
|
||||
"stripe_account_id": None,
|
||||
"account_status": "not_configured",
|
||||
"accepts_payments": False,
|
||||
"needs_setup": True
|
||||
}
|
||||
|
||||
|
||||
@router.post("/setup")
|
||||
async def setup_payments(
|
||||
setup_data: dict,
|
||||
vendor: Vendor = Depends(require_vendor_context()),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Set up Stripe payments for vendor."""
|
||||
payment_service = PaymentService(db)
|
||||
|
||||
vendor_data = {
|
||||
"business_name": vendor.name,
|
||||
"business_email": vendor.business_email,
|
||||
"business_phone": vendor.business_phone,
|
||||
**setup_data
|
||||
}
|
||||
|
||||
account_id = payment_service.create_vendor_stripe_account(vendor.id, vendor_data)
|
||||
onboarding_url = payment_service.create_onboarding_link(vendor.id)
|
||||
|
||||
return {
|
||||
"stripe_account_id": account_id,
|
||||
"onboarding_url": onboarding_url,
|
||||
"message": "Payment setup initiated. Complete onboarding to accept payments."
|
||||
}
|
||||
|
||||
|
||||
# app/api/v1/public/vendors/payments.py
|
||||
@router.post("/{vendor_id}/payments/create-intent")
|
||||
async def create_payment_intent(
|
||||
vendor_id: int,
|
||||
payment_data: dict,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create payment intent for customer order."""
|
||||
payment_service = PaymentService(db)
|
||||
|
||||
payment_intent = payment_service.create_payment_intent(
|
||||
vendor_id=vendor_id,
|
||||
order_id=payment_data['order_id'],
|
||||
amount_euros=Decimal(str(payment_data['amount'])),
|
||||
customer_email=payment_data['customer_email'],
|
||||
metadata=payment_data.get('metadata', {})
|
||||
)
|
||||
|
||||
return payment_intent
|
||||
|
||||
|
||||
@router.post("/webhooks/stripe")
|
||||
async def stripe_webhook(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Handle Stripe webhook events."""
|
||||
import stripe
|
||||
|
||||
payload = await request.body()
|
||||
sig_header = request.headers.get('stripe-signature')
|
||||
|
||||
try:
|
||||
event = stripe.Webhook.construct_event(
|
||||
payload, sig_header, settings.stripe_webhook_secret
|
||||
)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid payload")
|
||||
except stripe.error.SignatureVerificationError:
|
||||
raise HTTPException(status_code=400, detail="Invalid signature")
|
||||
|
||||
payment_service = PaymentService(db)
|
||||
payment_service.webhook_handler(event['type'], event['data'])
|
||||
|
||||
return {"status": "success"}
|
||||
```
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### Checkout Process
|
||||
|
||||
```javascript
|
||||
// frontend/js/shop/checkout.js
|
||||
class CheckoutManager {
|
||||
constructor(vendorId) {
|
||||
this.vendorId = vendorId;
|
||||
this.stripe = Stripe(STRIPE_PUBLISHABLE_KEY);
|
||||
this.elements = this.stripe.elements();
|
||||
this.paymentElement = null;
|
||||
}
|
||||
|
||||
async initializePayment(orderData) {
|
||||
// Create payment intent
|
||||
const response = await fetch(`/api/v1/public/vendors/${this.vendorId}/payments/create-intent`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
order_id: orderData.orderId,
|
||||
amount: orderData.total,
|
||||
customer_email: orderData.customerEmail
|
||||
})
|
||||
});
|
||||
|
||||
const { client_secret, amount_total, platform_fee } = await response.json();
|
||||
|
||||
// Display payment breakdown
|
||||
this.displayPaymentBreakdown(amount_total, platform_fee);
|
||||
|
||||
// Create payment element
|
||||
this.paymentElement = this.elements.create('payment', {
|
||||
clientSecret: client_secret
|
||||
});
|
||||
|
||||
this.paymentElement.mount('#payment-element');
|
||||
}
|
||||
|
||||
async confirmPayment(orderData) {
|
||||
const { error } = await this.stripe.confirmPayment({
|
||||
elements: this.elements,
|
||||
confirmParams: {
|
||||
return_url: `${window.location.origin}/shop/order-confirmation`,
|
||||
receipt_email: orderData.customerEmail
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
this.showPaymentError(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Updated Workflow Integration
|
||||
|
||||
### Enhanced Customer Purchase Workflow
|
||||
|
||||
```
|
||||
Customer adds products to cart
|
||||
↓
|
||||
Customer proceeds to checkout
|
||||
↓
|
||||
System creates Order (payment_status: pending)
|
||||
↓
|
||||
Frontend calls POST /api/v1/public/vendors/{vendor_id}/payments/create-intent
|
||||
↓
|
||||
PaymentService creates Stripe PaymentIntent with vendor destination
|
||||
↓
|
||||
Customer completes payment with Stripe Elements
|
||||
↓
|
||||
Stripe webhook confirms payment
|
||||
↓
|
||||
PaymentService updates Order (payment_status: paid, status: processing)
|
||||
↓
|
||||
Vendor receives order for fulfillment
|
||||
```
|
||||
|
||||
### Payment Configuration Workflow
|
||||
|
||||
```
|
||||
Vendor accesses payment settings
|
||||
↓
|
||||
POST /api/v1/vendor/payments/setup
|
||||
↓
|
||||
System creates Stripe Connect account
|
||||
↓
|
||||
Vendor completes Stripe onboarding
|
||||
↓
|
||||
Webhook updates account status to 'active'
|
||||
↓
|
||||
Vendor can now accept payments
|
||||
```
|
||||
|
||||
This integration provides secure, compliant payment processing while maintaining vendor isolation and enabling proper revenue distribution between vendors and the platform.
|
||||
@@ -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. 🚀
|
||||
@@ -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
|
||||
@@ -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.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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/partials/header.html`** ✅
|
||||
- Top navigation bar
|
||||
- Updated logout link to /admin/login
|
||||
|
||||
5. **`app/templates/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.
|
||||
@@ -1,392 +0,0 @@
|
||||
# Multi-Tenant Ecommerce Platform - Complete Naming Convention Guide
|
||||
|
||||
## 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.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Context-Based Naming
|
||||
- **Collections/Endpoints**: Use PLURAL (handle multiple items)
|
||||
- **Entities/Models**: Use SINGULAR (represent individual items)
|
||||
- **Domains/Services**: Use SINGULAR (focus on one domain area)
|
||||
|
||||
### 2. Terminology Standardization
|
||||
- Use **"inventory"** not "stock" (more business-friendly)
|
||||
- Use **"vendor"** not "shop" (multi-tenant architecture)
|
||||
- Use **"customer"** not "user" for end customers (clarity)
|
||||
|
||||
### 3. File Naming Patterns
|
||||
- **API files**: `entities.py` (plural)
|
||||
- **Model files**: `entity.py` (singular)
|
||||
- **Service files**: `entity_service.py` (singular + service)
|
||||
- **Exception files**: `entity.py` (singular domain)
|
||||
|
||||
## Detailed Naming Rules
|
||||
|
||||
### API Endpoint Files (PLURAL)
|
||||
**Rule**: API files handle collections of resources, so use plural names.
|
||||
|
||||
**Location**: `app/api/v1/*/`
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
app/api/v1/admin/
|
||||
├── vendors.py # Handles multiple vendors
|
||||
├── users.py # Handles multiple users
|
||||
└── dashboard.py # Exception: not a resource collection
|
||||
|
||||
app/api/v1/vendor/
|
||||
├── products.py # Handles vendor's products
|
||||
├── orders.py # Handles vendor's orders
|
||||
├── customers.py # Handles vendor's customers
|
||||
├── teams.py # Handles team members
|
||||
├── inventory.py # Handles inventory items
|
||||
└── settings.py # Exception: not a resource collection
|
||||
|
||||
app/api/v1/public/vendors/
|
||||
├── products.py # Public product catalog
|
||||
├── orders.py # Order placement
|
||||
└── auth.py # Exception: authentication service
|
||||
```
|
||||
|
||||
**Rationale**: REST endpoints typically operate on collections (`GET /products`, `POST /orders`).
|
||||
|
||||
### Database Model Files (SINGULAR)
|
||||
**Rule**: Model files represent individual entity definitions, so use singular names.
|
||||
|
||||
**Location**: `models/database/`
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
models/database/
|
||||
├── user.py # User, UserProfile classes
|
||||
├── vendor.py # Vendor, VendorUser, Role classes
|
||||
├── customer.py # Customer, CustomerAddress classes
|
||||
├── product.py # Product, ProductVariant classes
|
||||
├── order.py # Order, OrderItem classes
|
||||
├── inventory.py # Inventory, InventoryMovement classes
|
||||
├── marketplace.py # MarketplaceImportJob class
|
||||
└── admin.py # Admin-specific models
|
||||
```
|
||||
|
||||
**Class Names Within Files**:
|
||||
```python
|
||||
# models/database/product.py
|
||||
class Product(Base): # Singular
|
||||
class ProductVariant(Base): # Singular
|
||||
|
||||
# models/database/inventory.py
|
||||
class Inventory(Base): # Singular
|
||||
class InventoryMovement(Base): # Singular
|
||||
```
|
||||
|
||||
**Rationale**: Each model class represents a single entity instance in the database.
|
||||
|
||||
### Schema/Pydantic Model Files (SINGULAR)
|
||||
**Rule**: Schema files define validation for individual entities, so use singular names.
|
||||
|
||||
**Location**: `models/schema/`
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
models/schema/
|
||||
├── user.py # UserCreate, UserResponse classes
|
||||
├── vendor.py # VendorCreate, VendorResponse classes
|
||||
├── customer.py # CustomerCreate, CustomerResponse classes
|
||||
├── product.py # ProductCreate, ProductResponse classes
|
||||
├── order.py # OrderCreate, OrderResponse classes
|
||||
├── inventory.py # InventoryCreate, InventoryResponse classes
|
||||
├── marketplace.py # MarketplaceImportRequest class
|
||||
└── admin.py # Admin operation schemas
|
||||
```
|
||||
|
||||
**Class Names Within Files**:
|
||||
```python
|
||||
# models/schema/product.py
|
||||
class ProductCreate(BaseModel): # Singular entity
|
||||
class ProductUpdate(BaseModel): # Singular entity
|
||||
class ProductResponse(BaseModel): # Singular entity
|
||||
```
|
||||
|
||||
**Rationale**: Schema models validate individual entity data structures.
|
||||
|
||||
### Service Files (SINGULAR + "service")
|
||||
**Rule**: Service files handle business logic for one domain area, so use singular + "service".
|
||||
|
||||
**Location**: `services/`
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
services/
|
||||
├── auth_service.py # Authentication domain
|
||||
├── admin_service.py # Admin operations domain
|
||||
├── vendor_service.py # Vendor management domain
|
||||
├── customer_service.py # Customer operations domain
|
||||
├── team_service.py # Team management domain
|
||||
├── product_service.py # Product operations domain
|
||||
├── order_service.py # Order operations domain
|
||||
├── inventory_service.py # Inventory operations domain
|
||||
├── marketplace_service.py # Marketplace integration domain
|
||||
└── stats_service.py # Statistics domain
|
||||
```
|
||||
|
||||
**Class Names Within Files**:
|
||||
```python
|
||||
# services/product_service.py
|
||||
class ProductService: # Singular domain focus
|
||||
def create_product() # Operates on single product
|
||||
def get_products() # Can return multiple, but service is singular
|
||||
```
|
||||
|
||||
**Rationale**: Each service focuses on one business domain area.
|
||||
|
||||
### Exception Files (SINGULAR)
|
||||
**Rule**: Exception files handle errors for one domain area, so use singular names.
|
||||
|
||||
**Location**: `app/exceptions/`
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
app/exceptions/
|
||||
├── base.py # Base exception classes
|
||||
├── handler.py # Exception handlers
|
||||
├── auth.py # Authentication domain exceptions
|
||||
├── admin.py # Admin domain exceptions
|
||||
├── vendor.py # Vendor domain exceptions
|
||||
├── customer.py # Customer domain exceptions
|
||||
├── product.py # Product domain exceptions
|
||||
├── order.py # Order domain exceptions
|
||||
├── inventory.py # Inventory domain exceptions
|
||||
└── marketplace.py # Marketplace domain exceptions
|
||||
```
|
||||
|
||||
**Class Names Within Files**:
|
||||
```python
|
||||
# app/exceptions/product.py
|
||||
class ProductNotFoundException(ResourceNotFoundException):
|
||||
class ProductAlreadyExistsException(ConflictException):
|
||||
class ProductValidationException(ValidationException):
|
||||
```
|
||||
|
||||
**Rationale**: Exception files are domain-focused, not collection-focused.
|
||||
|
||||
### Middleware Files (DESCRIPTIVE)
|
||||
**Rule**: Middleware files use descriptive names based on their function.
|
||||
|
||||
**Location**: `middleware/`
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
middleware/
|
||||
├── auth.py # Authentication middleware
|
||||
├── vendor_context.py # Vendor context detection
|
||||
├── rate_limiter.py # Rate limiting functionality
|
||||
├── logging_middleware.py # Request logging
|
||||
└── decorators.py # Cross-cutting decorators
|
||||
```
|
||||
|
||||
**Rationale**: Middleware serves specific cross-cutting functions.
|
||||
|
||||
### Frontend Files
|
||||
**Rule**: Frontend files use context-appropriate naming.
|
||||
|
||||
**Location**: `frontend/`
|
||||
|
||||
**Examples**:
|
||||
```
|
||||
frontend/
|
||||
├── admin/
|
||||
│ ├── vendors.html # PLURAL - lists multiple vendors
|
||||
│ ├── users.html # PLURAL - lists multiple users
|
||||
│ └── dashboard.html # SINGULAR - one dashboard
|
||||
├── vendor/admin/
|
||||
│ ├── products.html # PLURAL - lists multiple products
|
||||
│ ├── orders.html # PLURAL - lists multiple orders
|
||||
│ ├── teams.html # PLURAL - lists team members
|
||||
│ └── dashboard.html # SINGULAR - one dashboard
|
||||
└── shop/
|
||||
├── products.html # PLURAL - product catalog
|
||||
├── product.html # SINGULAR - single product detail
|
||||
├── orders.html # PLURAL - order history
|
||||
└── cart.html # SINGULAR - one shopping cart
|
||||
```
|
||||
|
||||
**Rationale**:
|
||||
- List views are plural (show collections)
|
||||
- Detail views are singular (show individual items)
|
||||
- Functional views use descriptive names
|
||||
|
||||
## Terminology Standards
|
||||
|
||||
### Core Business Terms
|
||||
|
||||
| Use This | Not This | Context |
|
||||
|----------|----------|---------|
|
||||
| inventory | stock | All inventory management |
|
||||
| vendor | shop | Multi-tenant architecture |
|
||||
| customer | user | End customers (buyers) |
|
||||
| user | member | Platform/vendor team members |
|
||||
| team | staff | Vendor team members |
|
||||
| order | purchase | Customer orders |
|
||||
| product | item | Catalog products |
|
||||
|
||||
### Database Naming
|
||||
|
||||
**Table Names**: Use singular, lowercase with underscores
|
||||
```sql
|
||||
-- Correct
|
||||
inventory
|
||||
inventory_movements
|
||||
vendor_users
|
||||
|
||||
-- Incorrect
|
||||
inventories
|
||||
inventory_movement
|
||||
vendorusers
|
||||
```
|
||||
|
||||
**Column Names**: Use singular, descriptive names
|
||||
```sql
|
||||
-- Correct
|
||||
vendor_id
|
||||
inventory_level
|
||||
created_at
|
||||
|
||||
-- Incorrect
|
||||
vendors_id
|
||||
inventory_levels
|
||||
creation_time
|
||||
```
|
||||
|
||||
### API Endpoint Patterns
|
||||
|
||||
**Resource Collections**: Use plural nouns
|
||||
```
|
||||
GET /api/v1/vendor/products # List products
|
||||
POST /api/v1/vendor/products # Create product
|
||||
GET /api/v1/vendor/orders # List orders
|
||||
POST /api/v1/vendor/orders # Create order
|
||||
```
|
||||
|
||||
**Individual Resources**: Use singular in URL structure
|
||||
```
|
||||
GET /api/v1/vendor/products/{id} # Get single product
|
||||
PUT /api/v1/vendor/products/{id} # Update single product
|
||||
DELETE /api/v1/vendor/products/{id} # Delete single product
|
||||
```
|
||||
|
||||
**Non-Resource Endpoints**: Use descriptive names
|
||||
```
|
||||
GET /api/v1/vendor/dashboard/stats # Dashboard statistics
|
||||
POST /api/v1/vendor/auth/login # Authentication
|
||||
GET /api/v1/vendor/settings # Vendor settings
|
||||
```
|
||||
|
||||
## Variable and Function Naming
|
||||
|
||||
### Function Names
|
||||
```python
|
||||
# Correct - verb + singular object
|
||||
def create_product()
|
||||
def get_customer()
|
||||
def update_order()
|
||||
def delete_inventory_item()
|
||||
|
||||
# Correct - verb + plural when operating on collections
|
||||
def get_products()
|
||||
def list_customers()
|
||||
def bulk_update_orders()
|
||||
|
||||
# Incorrect
|
||||
def create_products() # Creates one product
|
||||
def get_customers() # Gets one customer
|
||||
```
|
||||
|
||||
### Variable Names
|
||||
```python
|
||||
# Correct - context-appropriate singular/plural
|
||||
product = get_product(id)
|
||||
products = get_products()
|
||||
customer_list = get_all_customers()
|
||||
inventory_count = len(inventory_items)
|
||||
|
||||
# Incorrect
|
||||
products = get_product(id) # Single item, should be singular
|
||||
product = get_products() # Multiple items, should be plural
|
||||
```
|
||||
|
||||
### Class Attributes
|
||||
```python
|
||||
# Correct - descriptive and consistent
|
||||
class Vendor:
|
||||
id: int
|
||||
name: str
|
||||
subdomain: str
|
||||
owner_user_id: int # Singular reference
|
||||
created_at: datetime
|
||||
|
||||
class Customer:
|
||||
vendor_id: int # Belongs to one vendor
|
||||
total_orders: int # Aggregate count
|
||||
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
|
||||
|
||||
1. **Developer Productivity**: Predictable file locations and naming patterns
|
||||
2. **Code Readability**: Clear understanding of file purposes and contents
|
||||
3. **Team Communication**: Shared vocabulary and terminology
|
||||
4. **Maintenance**: Easier to locate and update related functionality
|
||||
5. **Onboarding**: New developers quickly understand the codebase structure
|
||||
6. **Documentation**: Consistent terminology across all documentation
|
||||
7. **API Usability**: Predictable and intuitive API endpoint structures
|
||||
|
||||
## Enforcement
|
||||
|
||||
### Code Review Checklist
|
||||
- [ ] File names follow singular/plural conventions
|
||||
- [ ] Class names use appropriate terminology (inventory vs stock)
|
||||
- [ ] API endpoints use plural resource names
|
||||
- [ ] Database models use singular names
|
||||
- [ ] Variables names match their content (singular vs plural)
|
||||
|
||||
### Automated Checks
|
||||
Consider implementing linting rules or pre-commit hooks to enforce:
|
||||
- File naming patterns
|
||||
- Import statement consistency
|
||||
- Variable naming conventions
|
||||
- API endpoint patterns
|
||||
|
||||
This naming convention guide ensures consistent, maintainable, and intuitive code across the entire multi-tenant ecommerce platform.
|
||||
@@ -1,649 +0,0 @@
|
||||
# Admin Models Integration Guide
|
||||
|
||||
## What We've Added
|
||||
|
||||
You now have:
|
||||
|
||||
1. **Database Models** (`models/database/admin.py`):
|
||||
- `AdminAuditLog` - Track all admin actions
|
||||
- `AdminNotification` - System alerts for admins
|
||||
- `AdminSetting` - Platform-wide settings
|
||||
- `PlatformAlert` - System health alerts
|
||||
- `AdminSession` - Track admin login sessions
|
||||
|
||||
2. **Pydantic Schemas** (`models/schemas/admin.py`):
|
||||
- Request/response models for all admin operations
|
||||
- Validation for bulk operations
|
||||
- System health check schemas
|
||||
|
||||
3. **Services**:
|
||||
- `AdminAuditService` - Audit logging operations
|
||||
- `AdminSettingsService` - Platform settings management
|
||||
|
||||
4. **API Endpoints**:
|
||||
- `/api/v1/admin/audit` - Audit log endpoints
|
||||
- `/api/v1/admin/settings` - Settings management
|
||||
- `/api/v1/admin/notifications` - Notifications & alerts (stubs)
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Integration
|
||||
|
||||
### Step 1: Update Database
|
||||
|
||||
Add the new models to your database imports:
|
||||
|
||||
```python
|
||||
# models/database/__init__.py
|
||||
from .admin import (
|
||||
AdminAuditLog,
|
||||
AdminNotification,
|
||||
AdminSetting,
|
||||
PlatformAlert,
|
||||
AdminSession
|
||||
)
|
||||
```
|
||||
|
||||
Run database migration:
|
||||
```bash
|
||||
# Create migration
|
||||
alembic revision --autogenerate -m "Add admin models"
|
||||
|
||||
# Apply migration
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
### Step 2: Update Admin API Router
|
||||
|
||||
```python
|
||||
# app/api/v1/admin/__init__.py
|
||||
from fastapi import APIRouter
|
||||
from . import auth, vendors, users, dashboard, marketplace, audit, settings, notifications
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
# Include all admin routers
|
||||
router.include_router(auth.router)
|
||||
router.include_router(vendors.router)
|
||||
router.include_router(users.router)
|
||||
router.include_router(dashboard.router)
|
||||
router.include_router(marketplace.router)
|
||||
router.include_router(audit.router) # NEW
|
||||
router.include_router(settings.router) # NEW
|
||||
router.include_router(notifications.router) # NEW
|
||||
```
|
||||
|
||||
### Step 3: Add Audit Logging to Existing Admin Operations
|
||||
|
||||
Update your `admin_service.py` to log actions:
|
||||
|
||||
```python
|
||||
# app/services/admin_service.py
|
||||
from app.services.admin_audit_service import admin_audit_service
|
||||
|
||||
class AdminService:
|
||||
|
||||
def create_vendor_with_owner(
|
||||
self, db: Session, vendor_data: VendorCreate
|
||||
) -> Tuple[Vendor, User, str]:
|
||||
"""Create vendor with owner user account."""
|
||||
|
||||
# ... existing code ...
|
||||
|
||||
vendor, owner_user, temp_password = # ... your creation logic
|
||||
|
||||
# LOG THE ACTION
|
||||
admin_audit_service.log_action(
|
||||
db=db,
|
||||
admin_user_id=current_admin_id, # You'll need to pass this
|
||||
action="create_vendor",
|
||||
target_type="vendor",
|
||||
target_id=str(vendor.id),
|
||||
details={
|
||||
"vendor_code": vendor.vendor_code,
|
||||
"subdomain": vendor.subdomain,
|
||||
"owner_email": owner_user.email
|
||||
}
|
||||
)
|
||||
|
||||
return vendor, owner_user, temp_password
|
||||
|
||||
def toggle_vendor_status(
|
||||
self, db: Session, vendor_id: int, admin_user_id: int
|
||||
) -> Tuple[Vendor, str]:
|
||||
"""Toggle vendor status with audit logging."""
|
||||
|
||||
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||
old_status = vendor.is_active
|
||||
|
||||
# ... toggle logic ...
|
||||
|
||||
# LOG THE ACTION
|
||||
admin_audit_service.log_action(
|
||||
db=db,
|
||||
admin_user_id=admin_user_id,
|
||||
action="toggle_vendor_status",
|
||||
target_type="vendor",
|
||||
target_id=str(vendor_id),
|
||||
details={
|
||||
"old_status": "active" if old_status else "inactive",
|
||||
"new_status": "active" if vendor.is_active else "inactive"
|
||||
}
|
||||
)
|
||||
|
||||
return vendor, message
|
||||
```
|
||||
|
||||
### Step 4: Update API Endpoints to Pass Admin User ID
|
||||
|
||||
Your API endpoints need to pass the current admin's ID to service methods:
|
||||
|
||||
```python
|
||||
# app/api/v1/admin/vendors.py
|
||||
|
||||
@router.post("", response_model=VendorResponse)
|
||||
def create_vendor_with_owner(
|
||||
vendor_data: VendorCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_user),
|
||||
):
|
||||
"""Create vendor with audit logging."""
|
||||
|
||||
vendor, owner_user, temp_password = admin_service.create_vendor_with_owner(
|
||||
db=db,
|
||||
vendor_data=vendor_data,
|
||||
admin_user_id=current_admin.id # Pass admin ID for audit logging
|
||||
)
|
||||
|
||||
# Audit log is automatically created inside the service
|
||||
|
||||
return {
|
||||
**VendorResponse.model_validate(vendor).model_dump(),
|
||||
"owner_email": owner_user.email,
|
||||
"owner_username": owner_user.username,
|
||||
"temporary_password": temp_password,
|
||||
"login_url": f"{vendor.subdomain}.platform.com/vendor/login"
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{vendor_id}/status")
|
||||
def toggle_vendor_status(
|
||||
vendor_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_user),
|
||||
):
|
||||
"""Toggle vendor status with audit logging."""
|
||||
vendor, message = admin_service.toggle_vendor_status(
|
||||
db=db,
|
||||
vendor_id=vendor_id,
|
||||
admin_user_id=current_admin.id # Pass for audit
|
||||
)
|
||||
return {"message": message, "vendor": VendorResponse.model_validate(vendor)}
|
||||
```
|
||||
|
||||
### Step 5: Add Request Context to Audit Logs
|
||||
|
||||
To capture IP address and user agent, use FastAPI's Request object:
|
||||
|
||||
```python
|
||||
# app/api/v1/admin/vendors.py
|
||||
from fastapi import Request
|
||||
|
||||
@router.delete("/{vendor_id}")
|
||||
def delete_vendor(
|
||||
vendor_id: int,
|
||||
request: Request, # Add request parameter
|
||||
confirm: bool = Query(False),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_user),
|
||||
):
|
||||
"""Delete vendor with full audit trail."""
|
||||
|
||||
if not confirm:
|
||||
raise HTTPException(status_code=400, detail="Confirmation required")
|
||||
|
||||
# Get request metadata
|
||||
ip_address = request.client.host if request.client else None
|
||||
user_agent = request.headers.get("user-agent")
|
||||
|
||||
message = admin_service.delete_vendor(db, vendor_id)
|
||||
|
||||
# Log with full context
|
||||
admin_audit_service.log_action(
|
||||
db=db,
|
||||
admin_user_id=current_admin.id,
|
||||
action="delete_vendor",
|
||||
target_type="vendor",
|
||||
target_id=str(vendor_id),
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
details={"confirm": True}
|
||||
)
|
||||
|
||||
return {"message": message}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example: Platform Settings Usage
|
||||
|
||||
### Creating Default Settings
|
||||
|
||||
```python
|
||||
# scripts/init_platform_settings.py
|
||||
from app.core.database import SessionLocal
|
||||
from app.services.admin_settings_service import admin_settings_service
|
||||
from models.schemas.admin import AdminSettingCreate
|
||||
|
||||
db = SessionLocal()
|
||||
|
||||
# Create default platform settings
|
||||
settings = [
|
||||
AdminSettingCreate(
|
||||
key="max_vendors_allowed",
|
||||
value="1000",
|
||||
value_type="integer",
|
||||
category="system",
|
||||
description="Maximum number of vendors allowed on the platform",
|
||||
is_public=False
|
||||
),
|
||||
AdminSettingCreate(
|
||||
key="maintenance_mode",
|
||||
value="false",
|
||||
value_type="boolean",
|
||||
category="system",
|
||||
description="Enable maintenance mode (blocks all non-admin access)",
|
||||
is_public=True
|
||||
),
|
||||
AdminSettingCreate(
|
||||
key="vendor_trial_days",
|
||||
value="30",
|
||||
value_type="integer",
|
||||
category="system",
|
||||
description="Default trial period for new vendors (days)",
|
||||
is_public=False
|
||||
),
|
||||
AdminSettingCreate(
|
||||
key="stripe_publishable_key",
|
||||
value="pk_test_...",
|
||||
value_type="string",
|
||||
category="payments",
|
||||
description="Stripe publishable key",
|
||||
is_public=True
|
||||
),
|
||||
AdminSettingCreate(
|
||||
key="stripe_secret_key",
|
||||
value="sk_test_...",
|
||||
value_type="string",
|
||||
category="payments",
|
||||
description="Stripe secret key",
|
||||
is_encrypted=True,
|
||||
is_public=False
|
||||
)
|
||||
]
|
||||
|
||||
for setting_data in settings:
|
||||
try:
|
||||
admin_settings_service.upsert_setting(db, setting_data, admin_user_id=1)
|
||||
print(f"✓ Created setting: {setting_data.key}")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create {setting_data.key}: {e}")
|
||||
|
||||
db.close()
|
||||
```
|
||||
|
||||
### Using Settings in Your Code
|
||||
|
||||
```python
|
||||
# app/services/vendor_service.py
|
||||
from app.services.admin_settings_service import admin_settings_service
|
||||
|
||||
def can_create_vendor(db: Session) -> bool:
|
||||
"""Check if platform allows creating more vendors."""
|
||||
|
||||
max_vendors = admin_settings_service.get_setting_value(
|
||||
db=db,
|
||||
key="max_vendors_allowed",
|
||||
default=1000
|
||||
)
|
||||
|
||||
current_count = db.query(Vendor).count()
|
||||
|
||||
return current_count < max_vendors
|
||||
|
||||
|
||||
def is_maintenance_mode(db: Session) -> bool:
|
||||
"""Check if platform is in maintenance mode."""
|
||||
return admin_settings_service.get_setting_value(
|
||||
db=db,
|
||||
key="maintenance_mode",
|
||||
default=False
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### Admin Dashboard with Audit Logs
|
||||
|
||||
```html
|
||||
<!-- templates/admin/audit_logs.html -->
|
||||
<div x-data="auditLogs()" x-init="loadLogs()">
|
||||
<h1>Audit Logs</h1>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="filters">
|
||||
<select x-model="filters.action" @change="loadLogs()">
|
||||
<option value="">All Actions</option>
|
||||
<option value="create_vendor">Create Vendor</option>
|
||||
<option value="delete_vendor">Delete Vendor</option>
|
||||
<option value="toggle_vendor_status">Toggle Status</option>
|
||||
<option value="update_setting">Update Setting</option>
|
||||
</select>
|
||||
|
||||
<select x-model="filters.target_type" @change="loadLogs()">
|
||||
<option value="">All Targets</option>
|
||||
<option value="vendor">Vendors</option>
|
||||
<option value="user">Users</option>
|
||||
<option value="setting">Settings</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Logs Table -->
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>Admin</th>
|
||||
<th>Action</th>
|
||||
<th>Target</th>
|
||||
<th>Details</th>
|
||||
<th>IP Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="log in logs" :key="log.id">
|
||||
<tr>
|
||||
<td x-text="formatDate(log.created_at)"></td>
|
||||
<td x-text="log.admin_username"></td>
|
||||
<td>
|
||||
<span class="badge" x-text="log.action"></span>
|
||||
</td>
|
||||
<td x-text="`${log.target_type}:${log.target_id}`"></td>
|
||||
<td>
|
||||
<button @click="showDetails(log)">View</button>
|
||||
</td>
|
||||
<td x-text="log.ip_address"></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination">
|
||||
<button @click="previousPage()" :disabled="skip === 0">Previous</button>
|
||||
<span x-text="`Page ${currentPage} of ${totalPages}`"></span>
|
||||
<button @click="nextPage()" :disabled="!hasMore">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function auditLogs() {
|
||||
return {
|
||||
logs: [],
|
||||
filters: {
|
||||
action: '',
|
||||
target_type: '',
|
||||
admin_user_id: null
|
||||
},
|
||||
skip: 0,
|
||||
limit: 50,
|
||||
total: 0,
|
||||
|
||||
async loadLogs() {
|
||||
const params = new URLSearchParams({
|
||||
skip: this.skip,
|
||||
limit: this.limit,
|
||||
...this.filters
|
||||
});
|
||||
|
||||
const response = await apiClient.get(`/api/v1/admin/audit/logs?${params}`);
|
||||
this.logs = response.logs;
|
||||
this.total = response.total;
|
||||
},
|
||||
|
||||
showDetails(log) {
|
||||
// Show modal with full details
|
||||
console.log('Details:', log.details);
|
||||
},
|
||||
|
||||
formatDate(date) {
|
||||
return new Date(date).toLocaleString();
|
||||
},
|
||||
|
||||
get currentPage() {
|
||||
return Math.floor(this.skip / this.limit) + 1;
|
||||
},
|
||||
|
||||
get totalPages() {
|
||||
return Math.ceil(this.total / this.limit);
|
||||
},
|
||||
|
||||
get hasMore() {
|
||||
return this.skip + this.limit < this.total;
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
this.skip += this.limit;
|
||||
this.loadLogs();
|
||||
},
|
||||
|
||||
previousPage() {
|
||||
this.skip = Math.max(0, this.skip - this.limit);
|
||||
this.loadLogs();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Platform Settings Management
|
||||
|
||||
```html
|
||||
<!-- templates/admin/settings.html -->
|
||||
<div x-data="platformSettings()" x-init="loadSettings()">
|
||||
<h1>Platform Settings</h1>
|
||||
|
||||
<!-- Category Tabs -->
|
||||
<div class="tabs">
|
||||
<button
|
||||
@click="selectedCategory = 'system'"
|
||||
:class="{'active': selectedCategory === 'system'}"
|
||||
>System</button>
|
||||
<button
|
||||
@click="selectedCategory = 'security'"
|
||||
:class="{'active': selectedCategory === 'security'}"
|
||||
>Security</button>
|
||||
<button
|
||||
@click="selectedCategory = 'payments'"
|
||||
:class="{'active': selectedCategory === 'payments'}"
|
||||
>Payments</button>
|
||||
</div>
|
||||
|
||||
<!-- Settings List -->
|
||||
<div class="settings-list">
|
||||
<template x-for="setting in filteredSettings" :key="setting.id">
|
||||
<div class="setting-item">
|
||||
<div class="setting-header">
|
||||
<h3 x-text="setting.key"></h3>
|
||||
<span class="badge" x-text="setting.value_type"></span>
|
||||
</div>
|
||||
<p class="setting-description" x-text="setting.description"></p>
|
||||
|
||||
<div class="setting-value">
|
||||
<input
|
||||
type="text"
|
||||
:value="setting.value"
|
||||
@change="updateSetting(setting.key, $event.target.value)"
|
||||
>
|
||||
<span class="updated-at" x-text="`Updated: ${formatDate(setting.updated_at)}`"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Add New Setting -->
|
||||
<button @click="showAddModal = true" class="btn-primary">
|
||||
Add New Setting
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function platformSettings() {
|
||||
return {
|
||||
settings: [],
|
||||
selectedCategory: 'system',
|
||||
showAddModal: false,
|
||||
|
||||
async loadSettings() {
|
||||
const response = await apiClient.get('/api/v1/admin/settings');
|
||||
this.settings = response.settings;
|
||||
},
|
||||
|
||||
get filteredSettings() {
|
||||
if (!this.selectedCategory) return this.settings;
|
||||
return this.settings.filter(s => s.category === this.selectedCategory);
|
||||
},
|
||||
|
||||
async updateSetting(key, newValue) {
|
||||
try {
|
||||
await apiClient.put(`/api/v1/admin/settings/${key}`, {
|
||||
value: newValue
|
||||
});
|
||||
showNotification('Setting updated successfully', 'success');
|
||||
this.loadSettings();
|
||||
} catch (error) {
|
||||
showNotification('Failed to update setting', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
formatDate(date) {
|
||||
return new Date(date).toLocaleString();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing the New Features
|
||||
|
||||
### Test Audit Logging
|
||||
|
||||
```python
|
||||
# tests/test_admin_audit.py
|
||||
import pytest
|
||||
from app.services.admin_audit_service import admin_audit_service
|
||||
|
||||
def test_log_admin_action(db_session, test_admin_user):
|
||||
"""Test logging admin actions."""
|
||||
log = admin_audit_service.log_action(
|
||||
db=db_session,
|
||||
admin_user_id=test_admin_user.id,
|
||||
action="create_vendor",
|
||||
target_type="vendor",
|
||||
target_id="123",
|
||||
details={"vendor_code": "TEST"}
|
||||
)
|
||||
|
||||
assert log is not None
|
||||
assert log.action == "create_vendor"
|
||||
assert log.target_type == "vendor"
|
||||
assert log.details["vendor_code"] == "TEST"
|
||||
|
||||
def test_query_audit_logs(db_session, test_admin_user):
|
||||
"""Test querying audit logs with filters."""
|
||||
# Create test logs
|
||||
for i in range(5):
|
||||
admin_audit_service.log_action(
|
||||
db=db_session,
|
||||
admin_user_id=test_admin_user.id,
|
||||
action=f"test_action_{i}",
|
||||
target_type="test",
|
||||
target_id=str(i)
|
||||
)
|
||||
|
||||
# Query logs
|
||||
from models.schemas.admin import AdminAuditLogFilters
|
||||
filters = AdminAuditLogFilters(limit=10)
|
||||
logs = admin_audit_service.get_audit_logs(db_session, filters)
|
||||
|
||||
assert len(logs) == 5
|
||||
```
|
||||
|
||||
### Test Platform Settings
|
||||
|
||||
```python
|
||||
# tests/test_admin_settings.py
|
||||
def test_create_setting(db_session, test_admin_user):
|
||||
"""Test creating platform setting."""
|
||||
from models.schemas.admin import AdminSettingCreate
|
||||
|
||||
setting_data = AdminSettingCreate(
|
||||
key="test_setting",
|
||||
value="test_value",
|
||||
value_type="string",
|
||||
category="test"
|
||||
)
|
||||
|
||||
result = admin_settings_service.create_setting(
|
||||
db=db_session,
|
||||
setting_data=setting_data,
|
||||
admin_user_id=test_admin_user.id
|
||||
)
|
||||
|
||||
assert result.key == "test_setting"
|
||||
assert result.value == "test_value"
|
||||
|
||||
def test_get_setting_value_with_type_conversion(db_session):
|
||||
"""Test getting setting values with proper type conversion."""
|
||||
# Create integer setting
|
||||
setting_data = AdminSettingCreate(
|
||||
key="max_vendors",
|
||||
value="100",
|
||||
value_type="integer",
|
||||
category="system"
|
||||
)
|
||||
admin_settings_service.create_setting(db_session, setting_data, 1)
|
||||
|
||||
# Get value (should be converted to int)
|
||||
value = admin_settings_service.get_setting_value(db_session, "max_vendors")
|
||||
assert isinstance(value, int)
|
||||
assert value == 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
You now have a complete admin infrastructure with:
|
||||
|
||||
✅ **Audit Logging**: Track all admin actions for compliance
|
||||
✅ **Platform Settings**: Manage global configuration
|
||||
✅ **Notifications**: System alerts for admins (structure ready)
|
||||
✅ **Platform Alerts**: Health monitoring (structure ready)
|
||||
✅ **Session Tracking**: Monitor admin logins (structure ready)
|
||||
|
||||
### Next Steps
|
||||
|
||||
1. **Apply database migrations** to create new tables
|
||||
2. **Update admin API router** to include new endpoints
|
||||
3. **Add audit logging** to existing admin operations
|
||||
4. **Create default platform settings** using the script
|
||||
5. **Build frontend pages** for audit logs and settings
|
||||
6. **Implement notification service** (notifications.py stubs)
|
||||
7. **Add monitoring** for platform alerts
|
||||
|
||||
These additions make your platform production-ready with full compliance and monitoring capabilities!
|
||||
@@ -1,128 +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
|
||||
|
|
||||
+---shop
|
||||
| | cart.html
|
||||
| | checkout.html
|
||||
| | home.html
|
||||
| | product.html
|
||||
| | products.html
|
||||
| | search.html
|
||||
| |
|
||||
| \---account
|
||||
| addresses.html
|
||||
| login.html
|
||||
| orders.html
|
||||
| profile.html
|
||||
| register.html
|
||||
|
|
||||
\---vendor
|
||||
| dashboard.html
|
||||
| login.html
|
||||
|
|
||||
\---admin
|
||||
| customers.html
|
||||
| inventory.html
|
||||
| media.html
|
||||
| notifications.html
|
||||
| orders.html
|
||||
| payments.html
|
||||
| products.html
|
||||
| settings.html
|
||||
| teams.html
|
||||
|
|
||||
\---marketplace
|
||||
browse.html
|
||||
config.html
|
||||
imports.html
|
||||
selected.html
|
||||
|
||||
Reference in New Issue
Block a user