Files
orion/docs/backend/admin-integration-guide.md
Samir Boulahtit 0d7915c275 docs: update authentication dependency names across documentation
Updated all documentation to use correct authentication dependency names:
- HTML pages: get_current_admin_from_cookie_or_header, get_current_vendor_from_cookie_or_header, get_current_customer_from_cookie_or_header
- API endpoints: get_current_admin_api, get_current_vendor_api, get_current_customer_api

Changes:
- Updated authentication flow diagrams with correct dependency names for admin and vendor flows
- Added comprehensive customer/shop authentication flow documentation
- Updated cookie isolation architecture to show all three contexts (admin, vendor, shop)
- Expanded security boundary enforcement diagram to include shop routes
- Added customer cross-context prevention examples
- Updated all code examples in frontend and backend documentation
- Fixed import statements to use app.api.deps instead of app.core.auth

Files updated:
- docs/api/authentication-flow-diagrams.md (added customer flows)
- docs/frontend/admin/page-templates.md
- docs/frontend/admin/architecture.md
- docs/frontend/shared/ui-components.md
- docs/frontend/shared/sidebar.md
- docs/development/exception-handling.md
- docs/architecture/diagrams/vendor-domain-diagrams.md
- docs/backend/admin-integration-guide.md
- docs/backend/admin-feature-integration.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 18:21:23 +01:00

18 KiB

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:

# models/database/__init__.py
from .admin import (
    AdminAuditLog,
    AdminNotification,
    AdminSetting,
    PlatformAlert,
    AdminSession
)

Run database migration:

# Create migration
alembic revision --autogenerate -m "Add admin models"

# Apply migration
alembic upgrade head

Step 2: Update Admin API Router

# 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:

# 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:

# 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_api),
):
    """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_api),
):
    """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:

# 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_api),
):
    """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

# 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

# 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

<!-- 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

<!-- 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

# 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

# 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!