# Cloudflare Subdomain Automation Design

**Date:** 2025-11-19
**Status:** Design Complete - Ready for Implementation
**Scope:** Automate DNS record creation for dealer subdomains when they publish

---

## Overview

When dealers complete registration and click "Publish", automatically create DNS records in Cloudflare so their landing page is immediately accessible at their chosen subdomain (e.g., `bobsoil.acdev3.com`).

**Current State:**

- Dealers choose subdomain during registration (stored in database)
- Subdomain validated and reserved
- DNS configuration is manual

**Goal:**

- Automatic DNS record creation on publish
- Zero manual intervention for subdomains
- Static pre-generated pages (no DB lookups per visitor)
- Custom domains remain manual (out of scope for v1)

---

## Architecture

### Approach: Direct Cloudflare API + Next.js ISR

**On Publish:**

1. Dealer clicks "Publish" in Step 3 of onboarding
2. `/api/dealers/[id]/publish` endpoint:
   - Updates dealer status to `active`
   - Calls Cloudflare API to create A record
   - Triggers Next.js ISR revalidation
   - Returns success

**Runtime:**

- Request hits `bobsoil.acdev3.com`
- Next.js middleware extracts subdomain
- Serves pre-generated static page (zero DB lookups)

**Why this approach:**

- ✅ Simple - no extra services or queues
- ✅ Synchronous feedback to dealer
- ✅ Uses built-in Next.js features (middleware + ISR)
- ✅ Cloudflare handles SSL automatically

---

## Cloudflare API Integration

### API Credentials

Store in `.env.local` / `.env.production`:

```bash
CLOUDFLARE_API_TOKEN=xxx
CLOUDFLARE_ZONE_ID=xxx      # Zone ID for acdev3.com
SERVER_IP_ADDRESS=xxx        # Server IP for A records
```

**API Token Permissions:**

- Zone.DNS - Edit
- Zone.Zone - Read
- Scoped to `acdev3.com` zone only

### DNS Record Creation

Create A record via Cloudflare REST API:

```javascript
POST https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records

{
  "type": "A",
  "name": "bobsoil",           // Subdomain only (not full domain)
  "content": "1.2.3.4",        // Server IP (same for all dealers)
  "ttl": 1,                     // 1 = Auto (Cloudflare optimizes)
  "proxied": true               // Enable SSL + DDoS protection
}
```

**Key Points:**

- `proxied: true` → Cloudflare handles SSL automatically (orange cloud)
- TTL `1` → automatic (Cloudflare optimizes)
- All dealers point to same server IP
- Store DNS record ID in database for future updates/deletion
- Idempotent: check if record exists before creating

---

## Next.js Implementation

### Middleware for Subdomain Routing

**File:** `middleware.ts`

```typescript
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const hostname = request.headers.get('host') || '';

  // Extract subdomain: "bobsoil.acdev3.com" → "bobsoil"
  const subdomain = hostname.split('.')[0];

  // Skip if main domain or localhost
  if (subdomain === 'acdev3' || subdomain === 'localhost') {
    return NextResponse.next();
  }

  // Rewrite to dealer page with subdomain
  return NextResponse.rewrite(new URL(`/dealers/${subdomain}`, request.url));
}
```

### Dealer Page with ISR

**File:** `app/dealers/[subdomain]/page.tsx`

```typescript
export async function generateStaticParams() {
  // Pre-build pages for active dealers only
  const dealers = await prisma.dealer.findMany({
    where: { status: 'active' },
    select: { subdomain: true }
  })
  return dealers.map(d => ({ subdomain: d.subdomain! }))
}

export const revalidate = false // Cache forever, revalidate on-demand

export default async function DealerPage({
  params
}: {
  params: { subdomain: string }
}) {
  const dealer = await prisma.dealer.findUnique({
    where: { subdomain: params.subdomain }
  })

  if (!dealer) notFound()

  return <DealerTemplate dealer={dealer} />
}
```

### On-Demand Revalidation

**File:** `app/api/revalidate/route.ts`

```typescript
import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const { path } = await request.json();

  revalidatePath(path);

  return NextResponse.json({ revalidated: true });
}
```

**Trigger from publish endpoint:**

```typescript
await fetch(`${process.env.NEXTAUTH_URL}/api/revalidate`, {
  method: 'POST',
  body: JSON.stringify({ path: `/dealers/${subdomain}` }),
});
```

---

## Publish Endpoint Flow

### Updated `/api/dealers/[id]/publish`

```typescript
1. Validate dealer ownership (existing)
2. Check dealer hasn't already published (existing)

3. NEW: Create Cloudflare DNS record
   - Call Cloudflare API to create A record
   - Store DNS record ID in database
   - Handle errors (already exists, API failure, etc.)

4. NEW: Trigger ISR revalidation
   - Call /api/revalidate with dealer subdomain path
   - Generate static page for dealer

5. Update dealer status to 'active' (existing)
6. Return success

7. NEW: Error rollback
   - If any step fails after DNS creation, delete DNS record
   - Ensure atomic operation
```

### Error Handling Scenarios

| Scenario                  | Handling                                                                |
| ------------------------- | ----------------------------------------------------------------------- |
| DNS record already exists | Check if it points to our server IP; if yes, skip creation (idempotent) |
| Cloudflare API timeout    | Return error, dealer can retry publish                                  |
| ISR revalidation fails    | Log warning but continue (will work on next request)                    |
| Database update fails     | Rollback DNS record creation                                            |

### Transaction Safety

Wrap in try-catch with cleanup:

- **Success:** DNS + ISR + DB update all complete
- **Failure:** Delete DNS record, dealer stays in `registration_pending`

---

## Database Changes

### Schema Addition

Add to `Dealer` model in `prisma/schema.prisma`:

```prisma
model Dealer {
  // ... existing fields

  cloudflareRecordId  String?    // Store DNS record ID for updates/deletion
  publishedAt         DateTime?  // Track when dealer published
}
```

### Migration

```bash
npx prisma migrate dev --name add_cloudflare_record_id
```

---

## File Structure

### New Files to Create

```
lib/cloudflare-api.ts              # Cloudflare API wrapper functions
lib/isr-revalidation.ts            # ISR trigger helper
app/dealers/[subdomain]/page.tsx   # Dealer landing page route
app/api/revalidate/route.ts        # ISR revalidation endpoint
middleware.ts                       # Subdomain detection (if not exists)
```

### Existing Files to Modify

```
app/api/dealers/[id]/publish/route.ts  # Add DNS creation + ISR steps
prisma/schema.prisma                    # Add cloudflareRecordId field
.env.local / .env.production           # Add Cloudflare credentials
```

---

## Testing Strategy

### Unit Tests

**`lib/cloudflare-api.ts`** - Mock Cloudflare API responses

- Test successful DNS record creation
- Test idempotent behavior (record already exists)
- Test API errors (auth failure, rate limits, timeouts)
- Test response parsing

**`app/api/dealers/[id]/publish/route.ts`** - Integration tests

- Mock Cloudflare API + Prisma
- Test full publish flow
- Test rollback on failure
- Test duplicate publish prevention

### Manual Testing (Development)

1. Create test dealer with subdomain "testdealer"
2. Click publish
3. Verify DNS record created in Cloudflare dashboard
4. Wait 30-60 seconds for DNS propagation
5. Visit `testdealer.acdev3.com` → should see dealer page
6. Test ISR: update dealer info, trigger revalidation, verify changes

### Staging Environment

- Test on `acdev3.com` before production
- Test multiple dealers publishing simultaneously
- Test DNS propagation timing
- Test rollback scenarios

### Production Monitoring

- Log all Cloudflare API calls (success/failure)
- Alert on publish failures
- Track DNS record creation count
- Monitor ISR revalidation success rate

---

## Rollout Plan

### Phase 1: Development (acdev3.com)

- Implement Cloudflare API wrapper
- Add DNS creation to publish endpoint
- Test with test dealers on `acdev3.com`
- Verify DNS propagation and ISR revalidation

### Phase 2: Staging Validation

- Test multiple dealer publishes
- Verify DNS propagation timing
- Test error scenarios and rollbacks
- Load test with concurrent publishes

### Phase 3: Production (shopamsoil.com / myamsoil.com)

- Update environment variables for production domains
- Same code, different Cloudflare zone IDs
- Monitor first 10-20 dealer publishes closely
- Have rollback plan ready

---

## Security Considerations

- **API Token:** Store securely, never commit to repository
- **Zone Scoping:** Token scoped to specific zone only
- **Rate Limiting:** Cloudflare API has rate limits, implement retry logic
- **Validation:** Verify dealer ownership before DNS operations
- **Rollback:** Always clean up DNS records on failure

---

## Performance Considerations

- **DNS Propagation:** 30-60 seconds typical, inform dealers
- **ISR Generation:** First visit after revalidation may be slower
- **Cloudflare API Latency:** 200-500ms typical, acceptable for publish action
- **Static Pages:** Zero DB lookups per visitor after generation

---

## Notes

- Custom domains remain manual (out of scope for v1)
- All dealer subdomains point to same server IP
- Cloudflare proxy enabled for all records (SSL + security)
- ISR details will evolve as application is built out
- See separate document for future enhancements
