# VENDOR THEME BACKEND - PROPER ARCHITECTURE IMPLEMENTATION

## Overview

This implementation follows your **complete backend architecture** with:
- ✅ **Separation of Concerns** - Service layer handles business logic
- ✅ **Exception Management** - Custom exceptions for all error cases
- ✅ **Proper Layering** - Database → Service → API → Frontend
- ✅ **Naming Conventions** - Follows your established patterns
- ✅ **No Business Logic in Endpoints** - All logic in service layer

---

## 📦 File Structure

```
app/
├── exceptions/
│   ├── __init__.py           # ← UPDATE (add vendor_theme imports)
│   └── vendor_theme.py       # ← NEW (custom exceptions)
├── services/
│   └── vendor_theme_service.py  # ← NEW (business logic)
└── api/v1/admin/
    ├── __init__.py           # ← UPDATE (register router)
    ├── pages.py              # ← UPDATE (add theme route)
    └── vendor_themes.py      # ← NEW (endpoints only)

models/
└── schema/
    └── vendor_theme.py       # ← Already created (Pydantic)

static/admin/js/
└── vendor-theme.js           # ← Already created (Alpine.js)

app/templates/admin/
└── vendor-theme.html         # ← Already created (HTML)
```

---

## 🏗️ Architecture Layers

### Layer 1: Exceptions (Error Handling)
**File:** `app/exceptions/vendor_theme.py`

```python
# Custom exceptions for domain-specific errors
- VendorThemeNotFoundException
- ThemePresetNotFoundException
- InvalidThemeDataException
- ThemeValidationException
- InvalidColorFormatException
- InvalidFontFamilyException
- ThemeOperationException
```

**Key Points:**
- ✅ Extends base exceptions (`ResourceNotFoundException`, etc.)
- ✅ Provides specific error codes
- ✅ Includes detailed error information
- ✅ No HTTP status codes (that's endpoint layer)

---

### Layer 2: Service (Business Logic)
**File:** `app/services/vendor_theme_service.py`

**Responsibilities:**
- ✅ Get/Create/Update/Delete themes
- ✅ Apply presets
- ✅ Validate theme data
- ✅ Database operations
- ✅ Raise custom exceptions

**Does NOT:**
- ❌ Handle HTTP requests/responses
- ❌ Raise HTTPException
- ❌ Know about FastAPI

**Example Service Method:**
```python
def update_theme(
    self,
    db: Session,
    vendor_code: str,
    theme_data: VendorThemeUpdate
) -> VendorTheme:
    """Update theme - returns VendorTheme or raises exception."""
    
    try:
        vendor = self._get_vendor_by_code(db, vendor_code)
        theme = # ... get or create theme
        
        # Validate
        self._validate_theme_data(theme_data)
        
        # Update
        self._apply_theme_updates(theme, theme_data)
        
        db.commit()
        return theme
        
    except CustomException:
        raise  # Re-raise custom exceptions
        
    except Exception as e:
        db.rollback()
        raise ThemeOperationException(...)  # Wrap generic errors
```

---

### Layer 3: API Endpoints (HTTP Interface)
**File:** `app/api/v1/admin/vendor_themes.py`

**Responsibilities:**
- ✅ Handle HTTP requests
- ✅ Call service methods
- ✅ Convert exceptions to HTTP responses
- ✅ Return JSON responses

**Does NOT:**
- ❌ Contain business logic
- ❌ Validate data (service does this)
- ❌ Access database directly

**Example Endpoint:**
```python
@router.put("/{vendor_code}")
async def update_vendor_theme(
    vendor_code: str,
    theme_data: VendorThemeUpdate,
    db: Session = Depends(get_db),
    current_admin: User = Depends(get_current_admin_user)
):
    """Endpoint - just calls service and handles errors."""
    
    try:
        # Call service (all logic there)
        theme = vendor_theme_service.update_theme(db, vendor_code, theme_data)
        
        # Return response
        return theme.to_dict()
        
    except VendorNotFoundException:
        raise HTTPException(status_code=404, detail="...")
        
    except ThemeValidationException as e:
        raise HTTPException(status_code=422, detail=e.message)
        
    except Exception as e:
        logger.error(...)
        raise HTTPException(status_code=500, detail="...")
```

---

## 📥 Installation

### Step 1: Install Exception Layer

```bash
# 1. Copy exception file
cp vendor_theme_exceptions.py app/exceptions/vendor_theme.py

# 2. Update exceptions __init__.py
cp exceptions__init__-updated.py app/exceptions/__init__.py
```

**Verify:**
```python
from app.exceptions import VendorThemeNotFoundException  # Should work
```

---

### Step 2: Install Service Layer

```bash
# Copy service file
cp vendor_theme_service.py app/services/vendor_theme_service.py
```

**Verify:**
```python
from app.services.vendor_theme_service import vendor_theme_service  # Should work
```

---

### Step 3: Install API Layer

```bash
# Copy API endpoints
cp vendor_themes_endpoints.py app/api/v1/admin/vendor_themes.py
```

**Verify:**
```python
from app.api.v1.admin import vendor_themes  # Should work
```

---

### Step 4: Register Router

```bash
# Update API router
cp __init__-updated.py app/api/v1/admin/__init__.py
```

**Changes:**
```python
# Added:
from . import vendor_themes
router.include_router(vendor_themes.router, tags=["admin-vendor-themes"])
```

---

### Step 5: Update Frontend Router

```bash
# Update pages router
cp pages-updated.py app/api/v1/admin/pages.py
```

**Added Route:**
```python
@router.get("/vendors/{vendor_code}/theme")
async def admin_vendor_theme_page(...):
    return templates.TemplateResponse("admin/vendor-theme.html", ...)
```

---

### Step 6: Copy Frontend Files (Already Done)

```bash
# JavaScript
cp vendor-theme-alpine.js static/admin/js/vendor-theme.js

# HTML Template
cp vendor-theme.html app/templates/admin/vendor-theme.html

# Pydantic Schemas
cp vendor_theme_schemas.py models/schema/vendor_theme.py

# Theme Presets
cp theme_presets.py app/core/theme_presets.py
```

---

## 🔄 Data Flow

### Complete Request Flow

```
1. User clicks "Save Theme" button
   ↓
2. JavaScript (vendor-theme.js)
   - apiClient.put('/api/v1/admin/vendor-themes/VENDOR001', data)
   ↓
3. API Endpoint (vendor_themes.py)
   - Receives HTTP PUT request
   - Validates admin authentication
   - Calls service layer
   ↓
4. Service Layer (vendor_theme_service.py)
   - Validates theme data
   - Gets vendor from database
   - Creates/updates VendorTheme
   - Commits transaction
   - Returns VendorTheme object
   ↓
5. API Endpoint
   - Converts VendorTheme to dict
   - Returns JSON response
   ↓
6. JavaScript
   - Receives response
   - Shows success toast
   - Updates UI
```

### Error Flow

```
1. Service detects invalid color
   ↓
2. Service raises InvalidColorFormatException
   ↓
3. API endpoint catches exception
   ↓
4. API converts to HTTPException(422)
   ↓
5. FastAPI returns JSON error
   ↓
6. JavaScript catches error
   ↓
7. Shows error toast to user
```

---

## ✅ Architecture Compliance

### Separation of Concerns ✅

| Layer | Responsibilities | ✅ |
|-------|-----------------|-----|
| **Exceptions** | Define error types | ✅ |
| **Service** | Business logic, validation, DB operations | ✅ |
| **API** | HTTP handling, auth, error conversion | ✅ |
| **Frontend** | User interaction, API calls | ✅ |

### What Goes Where

```python
# ❌ WRONG - Business logic in endpoint
@router.put("/{vendor_code}")
async def update_theme(...):
    vendor = db.query(Vendor).filter(...).first()  # ❌ Direct DB
    if not vendor:
        raise HTTPException(404)  # ❌ Should be custom exception
    
    theme.colors = theme_data.colors  # ❌ Business logic
    if not self._is_valid_color(theme.colors['primary']):  # ❌ Validation
        raise HTTPException(422)
    
    db.commit()  # ❌ Transaction management
    return theme.to_dict()


# ✅ CORRECT - Clean separation
@router.put("/{vendor_code}")
async def update_theme(...):
    try:
        # Just call service
        theme = vendor_theme_service.update_theme(db, vendor_code, theme_data)
        return theme.to_dict()
    except VendorNotFoundException:
        raise HTTPException(404)
    except ThemeValidationException as e:
        raise HTTPException(422, detail=e.message)
```

---

## 🧪 Testing

### Unit Tests (Service Layer)

```python
# tests/unit/services/test_vendor_theme_service.py

def test_update_theme_validates_colors(service, db, vendor):
    """Test color validation."""
    theme_data = VendorThemeUpdate(
        colors={"primary": "not-a-color"}  # Invalid
    )
    
    with pytest.raises(InvalidColorFormatException):
        service.update_theme(db, vendor.vendor_code, theme_data)


def test_apply_preset_invalid_name(service, db, vendor):
    """Test invalid preset name."""
    with pytest.raises(ThemePresetNotFoundException) as exc_info:
        service.apply_theme_preset(db, vendor.vendor_code, "invalid")
    
    assert "invalid" in str(exc_info.value.message)
```

### Integration Tests (API Layer)

```python
# tests/integration/api/v1/admin/test_vendor_themes.py

def test_update_theme_endpoint(client, admin_headers, vendor):
    """Test theme update endpoint."""
    response = client.put(
        f"/api/v1/admin/vendor-themes/{vendor.vendor_code}",
        json={"colors": {"primary": "#ff0000"}},
        headers=admin_headers
    )
    
    assert response.status_code == 200
    assert response.json()["colors"]["primary"] == "#ff0000"


def test_update_theme_invalid_color(client, admin_headers, vendor):
    """Test validation error response."""
    response = client.put(
        f"/api/v1/admin/vendor-themes/{vendor.vendor_code}",
        json={"colors": {"primary": "invalid"}},
        headers=admin_headers
    )
    
    assert response.status_code == 422
    assert "invalid color" in response.json()["detail"].lower()
```

---

## 📊 Comparison: Before vs After

### Before (Original) ❌

```python
# vendor_themes_api.py (OLD)
@router.put("/{vendor_code}")
async def update_vendor_theme(vendor_code: str, theme_data: dict, ...):
    # ❌ Direct database access
    vendor = db.query(Vendor).filter(...).first()
    
    # ❌ Business logic in endpoint
    if not theme:
        theme = VendorTheme(vendor_id=vendor.id)
        db.add(theme)
    
    # ❌ Data manipulation in endpoint
    if "colors" in theme_data:
        theme.colors = theme_data["colors"]
    
    # ❌ Transaction management in endpoint
    db.commit()
    db.refresh(theme)
    
    # ❌ Generic exception
    except Exception as e:
        raise HTTPException(500, detail="Failed")
```

### After (Refactored) ✅

```python
# vendor_themes.py (NEW - API Layer)
@router.put("/{vendor_code}")
async def update_vendor_theme(vendor_code: str, theme_data: VendorThemeUpdate, ...):
    try:
        # ✅ Just call service
        theme = vendor_theme_service.update_theme(db, vendor_code, theme_data)
        return theme.to_dict()
    
    # ✅ Specific exception handling
    except VendorNotFoundException:
        raise HTTPException(404, detail="Vendor not found")
    except ThemeValidationException as e:
        raise HTTPException(422, detail=e.message)


# vendor_theme_service.py (NEW - Service Layer)
class VendorThemeService:
    def update_theme(self, db, vendor_code, theme_data):
        try:
            # ✅ Business logic here
            vendor = self._get_vendor_by_code(db, vendor_code)
            theme = self._get_or_create_theme(db, vendor)
            
            # ✅ Validation
            self._validate_theme_data(theme_data)
            
            # ✅ Data updates
            self._apply_theme_updates(theme, theme_data)
            
            # ✅ Transaction management
            db.commit()
            return theme
            
        # ✅ Custom exceptions
        except ValidationError:
            raise ThemeValidationException(...)
```

---

## 📚 Files Reference

### Backend Files (7 files)

1. **vendor_theme_exceptions.py** → `app/exceptions/vendor_theme.py`
   - Custom exception classes

2. **exceptions__init__-updated.py** → `app/exceptions/__init__.py`
   - Updated with vendor_theme imports

3. **vendor_theme_service.py** → `app/services/vendor_theme_service.py`
   - Business logic service

4. **vendor_themes_endpoints.py** → `app/api/v1/admin/vendor_themes.py`
   - API endpoints (thin layer)

5. **__init__-updated.py** → `app/api/v1/admin/__init__.py`
   - Router registration

6. **pages-updated.py** → `app/api/v1/admin/pages.py`
   - Frontend route

7. **vendor_theme_schemas.py** → `models/schema/vendor_theme.py`
   - Pydantic models

### Frontend Files (3 files)

8. **vendor-theme-alpine.js** → `static/admin/js/vendor-theme.js`
9. **vendor-theme.html** → `app/templates/admin/vendor-theme.html`
10. **theme_presets.py** → `app/core/theme_presets.py`

---

## ✅ Final Checklist

### Backend Architecture
- [ ] Exceptions in `app/exceptions/vendor_theme.py`
- [ ] Service in `app/services/vendor_theme_service.py`
- [ ] Endpoints in `app/api/v1/admin/vendor_themes.py`
- [ ] No business logic in endpoints
- [ ] No HTTPException in service
- [ ] Custom exceptions used throughout

### Frontend Architecture
- [ ] Alpine.js component with `...data()`
- [ ] Uses lowercase `apiClient`
- [ ] Has initialization guard
- [ ] Follows dashboard.js pattern

### Integration
- [ ] Router registered in `__init__.py`
- [ ] Frontend route in `pages.py`
- [ ] All exceptions imported
- [ ] Service imported in endpoints

---

**Your theme editor now follows proper backend architecture with complete separation of concerns!** 🎯
