# Quick Start: Custom Domains in 30 Minutes

This guide gets you from zero to working custom domains in 30 minutes.

## What You're Building

**Before:** 
- Vendors only accessible via `vendor1.platform.com`

**After:** 
- Vendors accessible via custom domains: `customdomain1.com` → Vendor 1
- Old subdomain method still works!

## Prerequisites

- FastAPI application with vendor context middleware (you have this ✓)
- PostgreSQL database (you have this ✓)
- Admin privileges to add database tables

## 5-Step Setup

### Step 1: Add Database Table (5 minutes)

Create and run this migration:

```sql
-- Create vendor_domains table
CREATE TABLE vendor_domains (
    id SERIAL PRIMARY KEY,
    vendor_id INTEGER NOT NULL REFERENCES vendors(id) ON DELETE CASCADE,
    domain VARCHAR(255) NOT NULL UNIQUE,
    is_primary BOOLEAN DEFAULT FALSE,
    is_active BOOLEAN DEFAULT TRUE,
    is_verified BOOLEAN DEFAULT FALSE,
    verification_token VARCHAR(100) UNIQUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX idx_domain_active ON vendor_domains(domain, is_active);
```

**Verify:**
```bash
psql your_database -c "\d vendor_domains"
```

### Step 2: Create Model (5 minutes)

Create `models/database/vendor_domain.py`:

```python
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from app.core.database import Base
from datetime import datetime, timezone

class VendorDomain(Base):
    __tablename__ = "vendor_domains"
    
    id = Column(Integer, primary_key=True)
    vendor_id = Column(Integer, ForeignKey("vendors.id", ondelete="CASCADE"))
    domain = Column(String(255), nullable=False, unique=True)
    is_primary = Column(Boolean, default=False)
    is_active = Column(Boolean, default=True)
    is_verified = Column(Boolean, default=False)
    verification_token = Column(String(100))
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    
    vendor = relationship("Vendor", back_populates="domains")
    
    @classmethod
    def normalize_domain(cls, domain: str) -> str:
        return domain.replace("https://", "").replace("http://", "").rstrip("/").lower()
```

Update `models/database/vendor.py`:
```python
# Add to Vendor class
domains = relationship("VendorDomain", back_populates="vendor")
```

### Step 3: Update Middleware (10 minutes)

Replace your `middleware/vendor_context.py` with this key section:

```python
from models.database.vendor_domain import VendorDomain

class VendorContextManager:
    @staticmethod
    def detect_vendor_context(request: Request) -> Optional[dict]:
        host = request.headers.get("host", "").split(":")[0]
        path = request.url.path
        
        # NEW: Priority 1 - Custom domain check
        from app.core.config import settings
        platform_domain = getattr(settings, 'platform_domain', 'platform.com')
        
        is_custom_domain = (
            host and 
            not host.endswith(f".{platform_domain}") and 
            host != platform_domain and
            "localhost" not in host and
            not host.startswith("admin.")
        )
        
        if is_custom_domain:
            return {
                "domain": VendorDomain.normalize_domain(host),
                "detection_method": "custom_domain",
                "host": host
            }
        
        # EXISTING: Priority 2 - Subdomain check
        if "." in host and not "localhost" in host:
            parts = host.split(".")
            if len(parts) >= 2 and parts[0] not in ["www", "admin", "api"]:
                return {
                    "subdomain": parts[0],
                    "detection_method": "subdomain",
                    "host": host
                }
        
        # EXISTING: Priority 3 - Path-based check
        if path.startswith("/vendor/"):
            path_parts = path.split("/")
            if len(path_parts) >= 3:
                return {
                    "subdomain": path_parts[2],
                    "detection_method": "path",
                    "path_prefix": f"/vendor/{path_parts[2]}"
                }
        
        return None
    
    @staticmethod
    def get_vendor_from_context(db: Session, context: dict) -> Optional[Vendor]:
        if not context:
            return None
        
        # NEW: Custom domain lookup
        if context.get("detection_method") == "custom_domain":
            vendor_domain = (
                db.query(VendorDomain)
                .filter(VendorDomain.domain == context["domain"])
                .filter(VendorDomain.is_active == True)
                .filter(VendorDomain.is_verified == True)
                .first()
            )
            if vendor_domain and vendor_domain.vendor.is_active:
                return vendor_domain.vendor
        
        # EXISTING: Subdomain/path lookup
        if "subdomain" in context:
            return (
                db.query(Vendor)
                .filter(func.lower(Vendor.subdomain) == context["subdomain"].lower())
                .filter(Vendor.is_active == True)
                .first()
            )
        
        return None
```

### Step 4: Add Config (2 minutes)

Edit `app/core/config.py`:

```python
class Settings(BaseSettings):
    # ... existing settings ...
    
    # Add this
    platform_domain: str = "platform.com"  # Change to YOUR domain
```

Update `.env`:
```
PLATFORM_DOMAIN=platform.com  # Change to YOUR domain
```

### Step 5: Test Locally (8 minutes)

**Add test data:**
```sql
-- Assuming you have a vendor with id=1
INSERT INTO vendor_domains (vendor_id, domain, is_active, is_verified)
VALUES (1, 'testdomain.local', true, true);
```

**Edit /etc/hosts:**
```bash
sudo nano /etc/hosts
# Add:
127.0.0.1 testdomain.local
```

**Start server:**
```bash
uvicorn main:app --reload
```

**Test in browser:**
```
http://testdomain.local:8000/
```

**Check logs for:**
```
✓ Vendor found via custom domain: testdomain.local → [Vendor Name]
```

## Done! 🎉

You now have custom domain support!

## Quick Test Checklist

- [ ] Can access vendor via custom domain: `testdomain.local:8000`
- [ ] Can still access via subdomain: `vendor1.localhost:8000`
- [ ] Can still access via path: `localhost:8000/vendor/vendor1/`
- [ ] Logs show correct detection method
- [ ] Database has vendor_domains table
- [ ] Model imports work

## What Works Now

✅ **Custom domains** → Checks vendor_domains table
✅ **Subdomains** → Checks vendors.subdomain (existing)
✅ **Path-based** → Development mode (existing)
✅ **Admin** → Still accessible
✅ **Vendor isolation** → Each vendor sees only their data

## Next Steps

### For Development:
1. Test with multiple custom domains
2. Add more test vendors
3. Verify queries are scoped correctly

### For Production:
1. Add admin API endpoints (see full guide)
2. Add DNS verification (see full guide)
3. Configure Nginx to accept all domains
4. Set up SSL strategy

## Common Issues & Fixes

**Issue:** "Vendor not found"
```sql
-- Check if domain exists and is verified
SELECT * FROM vendor_domains WHERE domain = 'testdomain.local';

-- Should show is_verified = true and is_active = true
```

**Issue:** Wrong vendor loaded
```sql
-- Check for duplicate domains
SELECT domain, COUNT(*) FROM vendor_domains GROUP BY domain HAVING COUNT(*) > 1;
```

**Issue:** Still using subdomain detection
```python
# Check middleware logs - should show:
# detection_method: "custom_domain"
# Not "subdomain"
```

## Production Checklist

Before going live:

- [ ] Update `PLATFORM_DOMAIN` in production .env
- [ ] Configure Nginx: `server_name _;`
- [ ] Set up SSL strategy (Cloudflare recommended)
- [ ] Add proper DNS verification (security!)
- [ ] Add admin UI for domain management
- [ ] Test with real custom domain
- [ ] Monitor logs for errors
- [ ] Set up rollback plan

## Files Changed

**New files:**
- `models/database/vendor_domain.py`

**Modified files:**
- `middleware/vendor_context.py` (added custom domain logic)
- `app/core/config.py` (added platform_domain setting)
- `models/database/vendor.py` (added domains relationship)

**Database:**
- Added `vendor_domains` table

## Architecture Diagram

```
Request: customdomain1.com
    ↓
Nginx: Accepts all domains
    ↓
FastAPI Middleware:
    Is it custom domain? YES
    Query vendor_domains table
    Find vendor_id = 1
    Load Vendor 1
    ↓
Route Handler:
    Use request.state.vendor (Vendor 1)
    All queries scoped to Vendor 1
    ↓
Response: Vendor 1's shop
```

## Help & Resources

**Full Guides:**
- `/mnt/user-data/outputs/CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md` - Complete guide
- `/mnt/user-data/outputs/ARCHITECTURE_DIAGRAMS.md` - Visual diagrams
- `/mnt/user-data/outputs/IMPLEMENTATION_CHECKLIST.md` - Detailed checklist

**Files Generated:**
- `vendor_domain_model.py` - Complete model code
- `updated_vendor_context.py` - Complete middleware code
- `vendor_domains_api.py` - Admin API endpoints
- `migration_vendor_domains.py` - Database migration

## Timeline

- ✅ Step 1: 5 minutes (database)
- ✅ Step 2: 5 minutes (model)
- ✅ Step 3: 10 minutes (middleware)
- ✅ Step 4: 2 minutes (config)
- ✅ Step 5: 8 minutes (testing)

**Total: 30 minutes** ⏱️

---

**Questions?** Check the full implementation guide or review the architecture diagrams!
