# Vendor Domains - Refactored Architecture Implementation Guide

## Overview

This document explains the refactored vendor domains implementation that follows your application's architecture patterns with proper separation of concerns.

## Architecture Summary

The implementation follows these key principles:

1. **Separation of Concerns**: Endpoints, service layer, schemas, and database models are separated
2. **Exception-Based Error Handling**: Custom exceptions with proper HTTP status codes
3. **Service Layer Pattern**: Business logic isolated in service classes
4. **Pydantic Validation**: Input validation using Pydantic schemas
5. **Consistent Response Format**: Standardized response models

## File Structure

```
app/
├── api/
│   └── v1/
│       └── admin/
│           └── vendor_domains.py          # HTTP endpoints (NEW)
├── services/
│   └── vendor_domain_service.py           # Business logic (NEW)
├── exceptions/
│   ├── __init__.py                        # Updated exports
│   └── vendor_domain.py                   # Domain exceptions (NEW)
models/
├── schema/
│   └── vendor_domain.py                   # Pydantic schemas (NEW)
└── database/
    ├── vendor.py                          # Updated Vendor model
    └── vendor_domain.py                   # VendorDomain model
```

## Components

### 1. Exceptions (`app/exceptions/vendor_domain.py`)

Custom exceptions for domain operations:

```python
# Resource Not Found (404)
- VendorDomainNotFoundException

# Conflicts (409)
- VendorDomainAlreadyExistsException

# Validation (422)
- InvalidDomainFormatException
- ReservedDomainException

# Business Logic (400)
- DomainNotVerifiedException
- DomainVerificationFailedException
- DomainAlreadyVerifiedException
- MultiplePrimaryDomainsException
- MaxDomainsReachedException
- UnauthorizedDomainAccessException

# External Service (502)
- DNSVerificationException
```

**Key Features:**
- Inherit from appropriate base exceptions
- Include relevant context in `details` dict
- Proper HTTP status codes
- Clear, actionable error messages

### 2. Pydantic Schemas (`models/schema/vendor_domain.py`)

Input validation and response models:

```python
# Request Schemas
- VendorDomainCreate        # For adding domains
- VendorDomainUpdate        # For updating settings

# Response Schemas
- VendorDomainResponse              # Single domain
- VendorDomainListResponse          # List of domains
- DomainVerificationInstructions    # DNS instructions
- DomainVerificationResponse        # Verification result
- DomainDeletionResponse            # Deletion confirmation
```

**Validation Features:**
- Domain normalization (lowercase, remove protocol)
- Reserved subdomain checking
- Format validation using regex
- Field validators with custom error messages

### 3. Service Layer (`app/services/vendor_domain_service.py`)

Business logic and database operations:

```python
class VendorDomainService:
    # Core Operations
    add_domain()                    # Add custom domain
    get_vendor_domains()            # List vendor's domains
    get_domain_by_id()              # Get single domain
    update_domain()                 # Update settings
    delete_domain()                 # Remove domain
    
    # Verification
    verify_domain()                 # DNS verification
    get_verification_instructions() # Get DNS instructions
    
    # Private Helpers
    _get_vendor_by_id_or_raise()   # Vendor lookup
    _check_domain_limit()           # Enforce max domains
    _domain_exists()                # Check uniqueness
    _validate_domain_format()       # Format validation
    _unset_primary_domains()        # Primary domain logic
```

**Service Pattern:**
- All database operations in service layer
- Raises custom exceptions (not HTTPException)
- Transaction management (commit/rollback)
- Comprehensive logging
- Helper methods with `_` prefix for internal use

### 4. API Endpoints (`app/api/v1/admin/vendor_domains.py`)

HTTP layer for domain management:

```python
# Endpoints
POST   /vendors/{vendor_id}/domains              # Add domain
GET    /vendors/{vendor_id}/domains              # List domains
GET    /vendors/domains/{domain_id}              # Get domain
PUT    /vendors/domains/{domain_id}              # Update domain
DELETE /vendors/domains/{domain_id}              # Delete domain
POST   /vendors/domains/{domain_id}/verify       # Verify ownership
GET    /vendors/domains/{domain_id}/verification-instructions  # Get instructions
```

**Endpoint Pattern:**
- Only handle HTTP concerns (request/response)
- Delegate business logic to service layer
- Use Pydantic schemas for validation
- Proper dependency injection
- Comprehensive docstrings
- No direct database access

## Comparison: Old vs New

### Old Implementation Issues

```python
# ❌ Mixed concerns
@router.post("/{vendor_id}/domains")
def add_vendor_domain(...):
    # Validation in endpoint
    domain = VendorDomain.normalize_domain(domain_data.domain)
    
    # Business logic in endpoint
    if not domain or '/' in domain:
        raise HTTPException(400, "Invalid domain")
    
    # Database operations in endpoint
    existing = db.query(VendorDomain).filter(...).first()
    if existing:
        raise HTTPException(409, "Domain exists")
    
    # Direct database access
    domain = VendorDomain(...)
    db.add(domain)
    db.commit()
```

**Problems:**
- Business logic mixed with HTTP layer
- HTTPException instead of custom exceptions
- No service layer separation
- Direct database access in endpoints
- Validation scattered across endpoint
- Hard to test business logic

### New Implementation

```python
# ✅ Proper separation

# Endpoint (HTTP layer only)
@router.post("/{vendor_id}/domains", response_model=VendorDomainResponse)
def add_vendor_domain(
    vendor_id: int = Path(..., gt=0),
    domain_data: VendorDomainCreate = Body(...),  # Pydantic validation
    db: Session = Depends(get_db),
    current_admin: User = Depends(get_current_admin_user),
):
    """Add domain - delegates to service layer"""
    domain = vendor_domain_service.add_domain(db, vendor_id, domain_data)
    return VendorDomainResponse(...mapping...)

# Service (Business logic)
class VendorDomainService:
    def add_domain(self, db, vendor_id, domain_data):
        """Business logic and database operations"""
        # Verify vendor
        vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
        
        # Check limits
        self._check_domain_limit(db, vendor_id)
        
        # Validate format
        self._validate_domain_format(normalized_domain)
        
        # Check uniqueness
        if self._domain_exists(db, normalized_domain):
            raise VendorDomainAlreadyExistsException(...)  # Custom exception
        
        # Business logic
        if domain_data.is_primary:
            self._unset_primary_domains(db, vendor_id)
        
        # Database operations
        new_domain = VendorDomain(...)
        db.add(new_domain)
        db.commit()
        return new_domain

# Schema (Validation)
class VendorDomainCreate(BaseModel):
    domain: str
    is_primary: bool = False
    
    @field_validator('domain')
    def validate_domain(cls, v: str) -> str:
        """Normalize and validate domain"""
        # Validation logic here
        return normalized_domain
```

**Benefits:**
- Clean separation of concerns
- Custom exceptions with proper status codes
- Testable business logic
- Reusable service methods
- Centralized validation
- Easy to maintain

## Installation Steps

### 1. Add Exception File

```bash
# Create new exception file
app/exceptions/vendor_domain.py
```

Copy content from `vendor_domain_exceptions.py`

### 2. Update Exception Exports

```python
# app/exceptions/__init__.py
from .vendor_domain import (
    VendorDomainNotFoundException,
    VendorDomainAlreadyExistsException,
    # ... other exceptions
)

__all__ = [
    # ... existing exports
    "VendorDomainNotFoundException",
    "VendorDomainAlreadyExistsException",
    # ... other exports
]
```

### 3. Add Pydantic Schemas

```bash
# Create schema file
models/schema/vendor_domain.py
```

Copy content from `vendor_domain_schema.py`

### 4. Add Service Layer

```bash
# Create service file
app/services/vendor_domain_service.py
```

Copy content from `vendor_domain_service.py`

### 5. Replace Endpoint File

```bash
# Replace existing file
app/api/v1/admin/vendor_domains.py
```

Copy content from `vendor_domains.py`

### 6. Install DNS Library

```bash
pip install dnspython
```

Required for DNS verification functionality.

## Usage Examples

### Adding a Domain

```python
# Request
POST /api/v1/admin/vendors/1/domains
{
    "domain": "myshop.com",
    "is_primary": true
}

# Response (201)
{
    "id": 1,
    "vendor_id": 1,
    "domain": "myshop.com",
    "is_primary": true,
    "is_active": false,
    "is_verified": false,
    "ssl_status": "pending",
    "verification_token": "abc123...",
    "verified_at": null,
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-15T10:00:00Z"
}
```

### Domain Verification

```python
# Step 1: Get verification instructions
GET /api/v1/admin/vendors/domains/1/verification-instructions

# Response
{
    "domain": "myshop.com",
    "verification_token": "abc123xyz...",
    "instructions": {
        "step1": "Go to your domain's DNS settings",
        "step2": "Add a new TXT record with the following values:",
        "step3": "Wait for DNS propagation (5-15 minutes)",
        "step4": "Click 'Verify Domain' button"
    },
    "txt_record": {
        "type": "TXT",
        "name": "_wizamart-verify",
        "value": "abc123xyz...",
        "ttl": 3600
    }
}

# Step 2: Vendor adds DNS record
# _wizamart-verify.myshop.com TXT "abc123xyz..."

# Step 3: Verify domain
POST /api/v1/admin/vendors/domains/1/verify

# Response (200)
{
    "message": "Domain myshop.com verified successfully",
    "domain": "myshop.com",
    "verified_at": "2025-01-15T10:15:00Z",
    "is_verified": true
}
```

### Activating Domain

```python
# After verification, activate domain
PUT /api/v1/admin/vendors/domains/1
{
    "is_active": true
}

# Response (200)
{
    "id": 1,
    "vendor_id": 1,
    "domain": "myshop.com",
    "is_primary": true,
    "is_active": true,
    "is_verified": true,
    "ssl_status": "pending",
    "verified_at": "2025-01-15T10:15:00Z",
    ...
}
```

## Error Handling Examples

### Domain Already Exists

```python
POST /api/v1/admin/vendors/1/domains
{
    "domain": "existing.com"
}

# Response (409 Conflict)
{
    "error_code": "VENDOR_DOMAIN_ALREADY_EXISTS",
    "message": "Domain 'existing.com' is already registered",
    "status_code": 409,
    "details": {
        "domain": "existing.com",
        "existing_vendor_id": 2
    }
}
```

### Invalid Domain Format

```python
POST /api/v1/admin/vendors/1/domains
{
    "domain": "admin.example.com"  # Reserved subdomain
}

# Response (422 Validation Error)
{
    "error_code": "RESERVED_DOMAIN",
    "message": "Domain cannot use reserved subdomain: admin",
    "status_code": 422,
    "details": {
        "domain": "admin.example.com",
        "reserved_part": "admin"
    }
}
```

### Verification Failed

```python
POST /api/v1/admin/vendors/domains/1/verify

# Response (400 Bad Request)
{
    "error_code": "DOMAIN_VERIFICATION_FAILED",
    "message": "Domain verification failed for 'myshop.com': Verification token not found in DNS records",
    "status_code": 400,
    "details": {
        "domain": "myshop.com",
        "reason": "Verification token not found in DNS records"
    }
}
```

## Testing

### Unit Tests

```python
# tests/unit/services/test_vendor_domain_service.py
def test_add_domain_success(db_session):
    """Test successful domain addition"""
    service = VendorDomainService()
    domain_data = VendorDomainCreate(
        domain="test.com",
        is_primary=True
    )
    
    domain = service.add_domain(db_session, vendor_id=1, domain_data=domain_data)
    
    assert domain.domain == "test.com"
    assert domain.is_primary is True
    assert domain.is_verified is False


def test_add_domain_already_exists(db_session):
    """Test adding duplicate domain raises exception"""
    service = VendorDomainService()
    
    with pytest.raises(VendorDomainAlreadyExistsException):
        service.add_domain(db_session, vendor_id=1, domain_data=...)
```

### Integration Tests

```python
# tests/integration/api/test_vendor_domains.py
def test_add_domain_endpoint(client, admin_headers):
    """Test domain addition endpoint"""
    response = client.post(
        "/api/v1/admin/vendors/1/domains",
        json={"domain": "newshop.com", "is_primary": False},
        headers=admin_headers
    )
    
    assert response.status_code == 201
    data = response.json()
    assert data["domain"] == "newshop.com"
    assert data["is_verified"] is False


def test_verify_domain_not_found(client, admin_headers):
    """Test verification with non-existent domain"""
    response = client.post(
        "/api/v1/admin/vendors/domains/99999/verify",
        headers=admin_headers
    )
    
    assert response.status_code == 404
    assert response.json()["error_code"] == "VENDOR_DOMAIN_NOT_FOUND"
```

## Benefits of This Architecture

### 1. Maintainability
- Clear separation makes code easy to understand
- Changes isolated to appropriate layers
- Easy to locate and fix bugs

### 2. Testability
- Service layer can be unit tested independently
- Mock dependencies easily
- Integration tests for endpoints

### 3. Reusability
- Service methods can be called from anywhere
- Schemas reused across endpoints
- Exceptions standardized

### 4. Scalability
- Add new endpoints without duplicating logic
- Extend service layer for new features
- Easy to add caching, queuing, etc.

### 5. Error Handling
- Consistent error responses
- Proper HTTP status codes
- Detailed error information for debugging

## Next Steps

1. **Database Migration**: Create migration for vendor_domains table if not exists
2. **Middleware Update**: Update vendor detection middleware to check custom domains
3. **Frontend Integration**: Build UI for domain management
4. **SSL Automation**: Add automatic SSL certificate provisioning
5. **Monitoring**: Add logging and monitoring for domain operations
6. **Rate Limiting**: Implement rate limits for domain additions
7. **Webhooks**: Add webhooks for domain status changes

## Conclusion

This refactored implementation follows your application's architecture patterns:
- ✅ Proper separation of concerns
- ✅ Exception-based error handling
- ✅ Service layer for business logic
- ✅ Pydantic schemas for validation
- ✅ Clean, maintainable code
- ✅ Consistent with existing patterns (vendors.py example)

The code is now production-ready, maintainable, and follows best practices!
