# Cloudflare Subdomain Automation Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Automate DNS record creation in Cloudflare when dealers publish their landing pages, making subdomains immediately accessible without manual intervention.

**Architecture:** Direct Cloudflare API integration triggered on publish, with Next.js middleware for subdomain routing and ISR for static page generation. Synchronous flow with transaction rollback on failure.

**Tech Stack:** Cloudflare REST API, Next.js middleware, ISR (on-demand revalidation), Prisma ORM, TypeScript

---

## Task 1: Database Schema Update

**Files:**

- Modify: `prisma/schema.prisma`
- Create: `prisma/migrations/` (auto-generated)

**Step 1: Read the current schema**

Run: `cat prisma/schema.prisma`

Verify the `Dealer` model exists.

**Step 2: Add new fields to Dealer model**

Open `prisma/schema.prisma` and add after existing fields in the `Dealer` model:

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

**Step 3: Create migration**

Run: `npx prisma migrate dev --name add_cloudflare_fields_to_dealer`

Expected output: Migration created and applied successfully.

**Step 4: Verify schema**

Run: `npx prisma studio` (optional, just verify the fields exist in the schema)

**Step 5: Commit**

```bash
git add prisma/schema.prisma prisma/migrations/
git commit -m "db: add cloudflareRecordId and publishedAt to Dealer model"
```

---

## Task 2: Create Cloudflare API Wrapper

**Files:**

- Create: `lib/cloudflare-api.ts`

**Step 1: Write the failing test**

Create `lib/__tests__/cloudflare-api.test.ts`:

```typescript
import { createDNSRecord, deleteDNSRecord, recordExists } from '../cloudflare-api';

describe('Cloudflare API', () => {
  beforeEach(() => {
    process.env.CLOUDFLARE_API_TOKEN = 'test-token';
    process.env.CLOUDFLARE_ZONE_ID = 'test-zone-id';
    process.env.SERVER_IP_ADDRESS = '1.2.3.4';
  });

  it('should create a DNS record', async () => {
    const result = await createDNSRecord('testdealer');
    expect(result).toHaveProperty('id');
    expect(result).toHaveProperty('name', 'testdealer');
  });

  it('should check if record exists', async () => {
    const exists = await recordExists('testdealer');
    expect(typeof exists).toBe('boolean');
  });

  it('should delete a DNS record', async () => {
    const result = await deleteDNSRecord('record-id-123');
    expect(result).toBe(true);
  });

  it('should handle API errors gracefully', async () => {
    await expect(createDNSRecord(null as any)).rejects.toThrow();
  });
});
```

Run: `npm test -- lib/__tests__/cloudflare-api.test.ts`

Expected: Tests fail because `cloudflare-api.ts` doesn't exist.

**Step 2: Create the Cloudflare API wrapper**

Create `lib/cloudflare-api.ts`:

```typescript
import { v4 as uuidv4 } from 'uuid';

const CLOUDFLARE_BASE_URL = 'https://api.cloudflare.com/client/v4';

interface CloudflareRecord {
  id: string;
  type: string;
  name: string;
  content: string;
  ttl: number;
  proxied: boolean;
  created_on: string;
  modified_on: string;
}

interface CloudflareResponse<T> {
  success: boolean;
  errors: Array<{ message: string }>;
  result: T;
}

/**
 * Create an A record in Cloudflare for a dealer subdomain
 * @param subdomain - Subdomain name (e.g., "bobsoil")
 * @returns Record ID for future reference
 */
export async function createDNSRecord(subdomain: string): Promise<{ id: string; name: string }> {
  const { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID, SERVER_IP_ADDRESS } = process.env;

  if (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ZONE_ID || !SERVER_IP_ADDRESS) {
    throw new Error('Missing Cloudflare environment variables');
  }

  if (!subdomain || typeof subdomain !== 'string') {
    throw new Error('Invalid subdomain');
  }

  try {
    // Check if record already exists
    const exists = await recordExists(subdomain);
    if (exists) {
      // Return existing record ID (idempotent)
      const record = await getRecordByName(subdomain);
      return { id: record.id, name: record.name };
    }

    const response = await fetch(`${CLOUDFLARE_BASE_URL}/zones/${CLOUDFLARE_ZONE_ID}/dns_records`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        type: 'A',
        name: subdomain,
        content: SERVER_IP_ADDRESS,
        ttl: 1,
        proxied: true,
      }),
    });

    const data = (await response.json()) as CloudflareResponse<CloudflareRecord>;

    if (!data.success) {
      const errorMsg = data.errors[0]?.message || 'Unknown error';
      throw new Error(`Cloudflare API error: ${errorMsg}`);
    }

    return { id: data.result.id, name: data.result.name };
  } catch (error) {
    console.error('Failed to create Cloudflare DNS record:', error);
    throw error;
  }
}

/**
 * Check if a DNS record exists for a subdomain
 */
async function recordExists(subdomain: string): Promise<boolean> {
  try {
    const record = await getRecordByName(subdomain);
    return !!record;
  } catch {
    return false;
  }
}

/**
 * Get a DNS record by subdomain name
 */
async function getRecordByName(subdomain: string): Promise<CloudflareRecord> {
  const { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID } = process.env;

  if (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ZONE_ID) {
    throw new Error('Missing Cloudflare environment variables');
  }

  const response = await fetch(
    `${CLOUDFLARE_BASE_URL}/zones/${CLOUDFLARE_ZONE_ID}/dns_records?name=${subdomain}`,
    {
      headers: {
        Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
      },
    }
  );

  const data = (await response.json()) as CloudflareResponse<CloudflareRecord[]>;

  if (!data.success) {
    throw new Error(`Cloudflare API error: ${data.errors[0]?.message || 'Unknown error'}`);
  }

  const record = data.result[0];
  if (!record) {
    throw new Error(`DNS record not found for ${subdomain}`);
  }

  return record;
}

/**
 * Delete a DNS record by ID
 */
export async function deleteDNSRecord(recordId: string): Promise<boolean> {
  const { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID } = process.env;

  if (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ZONE_ID) {
    throw new Error('Missing Cloudflare environment variables');
  }

  if (!recordId || typeof recordId !== 'string') {
    throw new Error('Invalid record ID');
  }

  try {
    const response = await fetch(
      `${CLOUDFLARE_BASE_URL}/zones/${CLOUDFLARE_ZONE_ID}/dns_records/${recordId}`,
      {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`,
        },
      }
    );

    const data = (await response.json()) as CloudflareResponse<{ id: string }>;

    if (!data.success) {
      console.error('Failed to delete Cloudflare record:', data.errors);
      return false;
    }

    return true;
  } catch (error) {
    console.error('Error deleting Cloudflare DNS record:', error);
    return false;
  }
}
```

**Step 3: Run tests to verify they pass**

Run: `npm test -- lib/__tests__/cloudflare-api.test.ts`

Expected: Tests may pass with mocking, but real API calls will fail without real credentials.

**Step 4: Add environment variable documentation**

Ensure `.env.local` has these variables documented. Run:

```bash
grep -E "CLOUDFLARE_|SERVER_IP" .env.local
```

If not present, add to `.env.local`:

```
CLOUDFLARE_API_TOKEN=your_token_here
CLOUDFLARE_ZONE_ID=your_zone_id_here
SERVER_IP_ADDRESS=your_server_ip_here
```

**Step 5: Commit**

```bash
git add lib/cloudflare-api.ts lib/__tests__/cloudflare-api.test.ts
git commit -m "feat: add Cloudflare DNS API wrapper with create/delete operations"
```

---

## Task 3: Create ISR Revalidation Helper

**Files:**

- Create: `lib/isr-revalidation.ts`

**Step 1: Write the failing test**

Create `lib/__tests__/isr-revalidation.test.ts`:

```typescript
import { triggerISRRevalidation } from '../isr-revalidation';

describe('ISR Revalidation', () => {
  beforeEach(() => {
    process.env.NEXTAUTH_URL = 'http://localhost:3000';
  });

  it('should trigger ISR revalidation for a dealer path', async () => {
    const result = await triggerISRRevalidation('testdealer');
    expect(result).toBe(true);
  });

  it('should handle invalid subdomain', async () => {
    await expect(triggerISRRevalidation('')).rejects.toThrow();
  });
});
```

Run: `npm test -- lib/__tests__/isr-revalidation.test.ts`

Expected: Tests fail because module doesn't exist.

**Step 2: Create ISR revalidation helper**

Create `lib/isr-revalidation.ts`:

```typescript
/**
 * Trigger Next.js ISR revalidation for a dealer page
 * @param subdomain - Dealer subdomain
 * @returns true if revalidation triggered successfully
 */
export async function triggerISRRevalidation(subdomain: string): Promise<boolean> {
  if (!subdomain || typeof subdomain !== 'string') {
    throw new Error('Invalid subdomain');
  }

  const { NEXTAUTH_URL } = process.env;

  if (!NEXTAUTH_URL) {
    throw new Error('NEXTAUTH_URL not configured');
  }

  try {
    const path = `/dealers/${subdomain}`;
    const response = await fetch(`${NEXTAUTH_URL}/api/revalidate`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ path }),
    });

    if (!response.ok) {
      console.error(`ISR revalidation failed: ${response.statusText}`);
      return false;
    }

    console.log(`ISR revalidation triggered for ${path}`);
    return true;
  } catch (error) {
    console.error('Error triggering ISR revalidation:', error);
    return false;
  }
}
```

**Step 3: Run tests**

Run: `npm test -- lib/__tests__/isr-revalidation.test.ts`

Expected: Tests pass or fail depending on network setup.

**Step 4: Commit**

```bash
git add lib/isr-revalidation.ts lib/__tests__/isr-revalidation.test.ts
git commit -m "feat: add ISR revalidation helper for dealer pages"
```

---

## Task 4: Create Subdomain Middleware

**Files:**

- Create: `middleware.ts` (if not already exists)

**Step 1: Check if middleware exists**

Run: `ls -la middleware.ts`

If file exists, back it up:

```bash
cp middleware.ts middleware.ts.backup
```

**Step 2: Create/update middleware**

Create or update `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 parts = hostname.split('.');
  const subdomain = parts[0];

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

  // Skip if IP address (for local development)
  if (/^\d+\.\d+\.\d+\.\d+$/.test(subdomain)) {
    return NextResponse.next();
  }

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

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};
```

**Step 3: Test middleware locally**

Add logging to verify subdomain extraction works:

```bash
echo "Testing middleware configuration..."
npm run build 2>&1 | head -20
```

Expected: Build succeeds without errors related to middleware.

**Step 4: Commit**

```bash
git add middleware.ts
git commit -m "feat: add subdomain extraction middleware"
```

---

## Task 5: Create Dealer Landing Page with ISR

**Files:**

- Create: `app/dealers/[subdomain]/page.tsx`
- Create: `app/dealers/[subdomain]/layout.tsx` (if needed)

**Step 1: Create directory structure**

Run: `mkdir -p app/dealers/[subdomain]`

**Step 2: Create the dealer page**

Create `app/dealers/[subdomain]/page.tsx`:

```typescript
import { notFound } from 'next/navigation';
import { prisma } from '@/lib/prisma';
import DealerTemplate from '@/components/DealerTemplate';

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

/**
 * Generate static params for all active dealers
 * This pre-builds pages at build time
 */
export async function generateStaticParams() {
  const dealers = await prisma.dealer.findMany({
    where: { status: 'active' },
    select: { subdomain: true },
  });

  return dealers.map((dealer) => ({
    subdomain: dealer.subdomain || '',
  }));
}

interface DealerPageProps {
  params: Promise<{ subdomain: string }>;
}

export default async function DealerPage({ params }: DealerPageProps) {
  const { subdomain } = await params;

  if (!subdomain) {
    notFound();
  }

  const dealer = await prisma.dealer.findUnique({
    where: { subdomain },
  });

  if (!dealer || dealer.status !== 'active') {
    notFound();
  }

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

**Step 3: Update DealerTemplate if needed**

Run: `grep -l "DealerTemplate" components/DealerTemplate.tsx`

Verify the component accepts a `dealer` prop. If it doesn't, update it to accept dealer data.

**Step 4: Test the page structure**

Run: `npm run build`

Expected: Build succeeds. Check for any TypeScript errors in dealer page.

**Step 5: Commit**

```bash
git add app/dealers/
git commit -m "feat: create dealer landing page with ISR support"
```

---

## Task 6: Create ISR Revalidation Endpoint

**Files:**

- Create: `app/api/revalidate/route.ts`

**Step 1: Create endpoint directory**

Run: `mkdir -p app/api/revalidate`

**Step 2: Create revalidation endpoint**

Create `app/api/revalidate/route.ts`:

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

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

    if (!path || typeof path !== 'string') {
      return NextResponse.json({ error: 'Missing path' }, { status: 400 });
    }

    // Revalidate the specific dealer path
    revalidatePath(path);

    console.log(`Revalidated path: ${path}`);

    return NextResponse.json({ revalidated: true, path });
  } catch (error) {
    console.error('Revalidation error:', error);
    return NextResponse.json({ error: 'Revalidation failed' }, { status: 500 });
  }
}
```

**Step 3: Test the endpoint**

Run: `npm run dev` in background, then test:

```bash
curl -X POST http://localhost:3000/api/revalidate \
  -H "Content-Type: application/json" \
  -d '{"path": "/dealers/testdealer"}'
```

Expected: Response with `{ revalidated: true, path: "/dealers/testdealer" }`

**Step 4: Commit**

```bash
git add app/api/revalidate/route.ts
git commit -m "feat: create ISR revalidation endpoint"
```

---

## Task 7: Update Publish Endpoint with DNS Creation and ISR

**Files:**

- Modify: `app/api/dealers/[id]/publish/route.ts`

**Step 1: Read the existing publish endpoint**

Run: `cat app/api/dealers/[id]/publish/route.ts`

Note the current implementation and structure.

**Step 2: Write integration test**

Create `app/api/dealers/__tests__/publish.test.ts`:

```typescript
import { POST } from '../[id]/publish/route';
import { prisma } from '@/lib/prisma';
import * as cloudflareApi from '@/lib/cloudflare-api';
import * as isrRevalidation from '@/lib/isr-revalidation';

jest.mock('@/lib/cloudflare-api');
jest.mock('@/lib/isr-revalidation');
jest.mock('@/lib/prisma');

describe('Publish Endpoint', () => {
  it('should create DNS record and trigger ISR on publish', async () => {
    const mockRequest = new Request('http://localhost/api/dealers/1/publish', {
      method: 'POST',
    });

    // Mock successful operations
    (cloudflareApi.createDNSRecord as jest.Mock).mockResolvedValue({
      id: 'dns-record-123',
      name: 'testdealer',
    });
    (isrRevalidation.triggerISRRevalidation as jest.Mock).mockResolvedValue(true);
    (prisma.dealer.update as jest.Mock).mockResolvedValue({
      id: '1',
      subdomain: 'testdealer',
      status: 'active',
    });

    // Would call endpoint here - implementation in next step
  });

  it('should rollback DNS record on database failure', async () => {
    // Test rollback scenario
  });
});
```

**Step 3: Update the publish endpoint**

Update `app/api/dealers/[id]/publish/route.ts`:

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { prisma } from '@/lib/prisma';
import { createDNSRecord, deleteDNSRecord } from '@/lib/cloudflare-api';
import { triggerISRRevalidation } from '@/lib/isr-revalidation';
import { authConfig } from '@/lib/auth';

export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  try {
    const { id } = await params;
    const session = await getServerSession(authConfig);

    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    }

    // Fetch dealer and verify ownership
    const dealer = await prisma.dealer.findUnique({ where: { id } });

    if (!dealer) {
      return NextResponse.json({ error: 'Dealer not found' }, { status: 404 });
    }

    if (dealer.userId !== session.user.id) {
      return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
    }

    // Check if already published
    if (dealer.status === 'active') {
      return NextResponse.json({ error: 'Already published' }, { status: 400 });
    }

    if (!dealer.subdomain) {
      return NextResponse.json({ error: 'No subdomain selected' }, { status: 400 });
    }

    let recordId: string | null = null;

    try {
      // Step 1: Create Cloudflare DNS record
      const dnsRecord = await createDNSRecord(dealer.subdomain);
      recordId = dnsRecord.id;

      // Step 2: Trigger ISR revalidation
      const revalidated = await triggerISRRevalidation(dealer.subdomain);
      if (!revalidated) {
        console.warn(`ISR revalidation failed for ${dealer.subdomain}, but continuing...`);
      }

      // Step 3: Update dealer status to active and store DNS record ID
      const updated = await prisma.dealer.update({
        where: { id },
        data: {
          status: 'active',
          cloudflareRecordId: recordId,
          publishedAt: new Date(),
        },
      });

      return NextResponse.json({
        success: true,
        message: 'Dealer published successfully',
        subdomain: updated.subdomain,
      });
    } catch (error) {
      // Rollback: Delete DNS record if created
      if (recordId) {
        console.log(`Rolling back DNS record ${recordId}`);
        await deleteDNSRecord(recordId);
      }

      throw error;
    }
  } catch (error) {
    console.error('Publish error:', error);

    const message = error instanceof Error ? error.message : 'Publish failed';
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
```

**Step 4: Run the endpoint test**

Run: `npm test -- app/api/dealers/__tests__/publish.test.ts`

Expected: Tests pass with mocked dependencies.

**Step 5: Manual integration test**

1. Start dev server: `npm run dev`
2. Navigate to registration
3. Complete registration with test subdomain
4. Click publish
5. Verify response includes success message
6. Check Cloudflare dashboard for DNS record (if credentials are real)

**Step 6: Commit**

```bash
git add app/api/dealers/[id]/publish/route.ts app/api/dealers/__tests__/
git commit -m "feat: integrate Cloudflare DNS creation and ISR revalidation into publish endpoint"
```

---

## Task 8: Add Error Handling and Rollback Testing

**Files:**

- Modify: `app/api/dealers/[id]/publish/route.ts` (already updated)
- Create: `lib/__tests__/publish-rollback.test.ts`

**Step 1: Write rollback scenario test**

Create `lib/__tests__/publish-rollback.test.ts`:

```typescript
import { createDNSRecord, deleteDNSRecord } from '../cloudflare-api';

jest.mock('../cloudflare-api');

describe('Publish Rollback Scenario', () => {
  it('should delete DNS record if publish fails after creation', async () => {
    const mockRecordId = 'dns-123';

    // Simulate successful DNS creation
    (createDNSRecord as jest.Mock).mockResolvedValue({ id: mockRecordId });

    // Simulate database failure
    const dnsRecord = await createDNSRecord('testdealer');

    // Now fail and trigger cleanup
    (deleteDNSRecord as jest.Mock).mockResolvedValue(true);
    const cleaned = await deleteDNSRecord(dnsRecord.id);

    expect(cleaned).toBe(true);
  });
});
```

**Step 2: Run rollback test**

Run: `npm test -- lib/__tests__/publish-rollback.test.ts`

Expected: Test passes, confirming rollback logic works.

**Step 3: Test error scenarios manually**

Test cases to verify:

- DNS record already exists → should be idempotent
- API timeout → should return error
- Database failure → DNS record should be rolled back
- ISR failure → should log warning but continue

**Step 4: Commit**

```bash
git add lib/__tests__/publish-rollback.test.ts
git commit -m "test: add rollback scenario tests for publish endpoint"
```

---

## Task 9: Update Environment Configuration

**Files:**

- Modify: `.env.local` (local development)
- Create: `docs/CLOUDFLARE_SETUP.md` (documentation)

**Step 1: Update .env.local**

Add or update in `.env.local`:

```bash
# Cloudflare API credentials
CLOUDFLARE_API_TOKEN=your_api_token_here
CLOUDFLARE_ZONE_ID=your_zone_id_here
SERVER_IP_ADDRESS=your_server_ip_here
```

**Step 2: Create Cloudflare setup documentation**

Create `docs/CLOUDFLARE_SETUP.md`:

````markdown
# Cloudflare Integration Setup

## Prerequisites

- Cloudflare account with API token
- Zone ID for your domain
- Server IP address

## Getting Cloudflare Credentials

### API Token

1. Go to Cloudflare Dashboard
2. Navigate to "API Tokens"
3. Create new token with permissions:
   - Zone.DNS - Edit
   - Zone.Zone - Read
4. Scope to specific zone (acdev3.com)
5. Copy token to `.env.local` as `CLOUDFLARE_API_TOKEN`

### Zone ID

1. Go to Cloudflare Dashboard
2. Select your domain (acdev3.com)
3. Copy "Zone ID" from sidebar
4. Set as `CLOUDFLARE_ZONE_ID` in `.env.local`

### Server IP Address

Get your server's public IP address:

```bash
curl https://api.ipify.org
```
````

Set as `SERVER_IP_ADDRESS` in `.env.local`

## Testing the Integration

```bash
npm run dev
# Navigate to /registration
# Complete registration and click "Publish"
# Check Cloudflare dashboard for DNS record
```

## Troubleshooting

- **API Token errors**: Verify token permissions are correct
- **Zone ID errors**: Confirm zone is active in Cloudflare
- **DNS creation fails**: Check Server IP Address is correct

````

**Step 3: Verify environment variables are set**

Run: `npm run dev` and watch for startup errors

Expected: No errors about missing Cloudflare environment variables.

**Step 4: Commit**

```bash
git add .env.local docs/CLOUDFLARE_SETUP.md
git commit -m "docs: add Cloudflare setup and configuration guide"
````

---

## Task 10: Integration and Full Flow Testing

**Files:**

- None (manual testing)

**Step 1: Full test flow - Happy path**

1. Start dev server: `npm run dev`
2. Navigate to `http://localhost:3000`
3. Complete registration:
   - Enter email
   - Complete OAuth
   - Choose subdomain (e.g., "testdealer")
   - Complete registration
4. Publish the dealer page
5. Wait 2-3 seconds
6. Verify response shows success
7. Check subdomain is accessible: `http://testdealer.localhost:3000` (or test domain)

**Step 2: Full test flow - Error scenario**

1. Remove CLOUDFLARE_API_TOKEN from .env.local
2. Try to publish again
3. Verify error message is displayed
4. Restore CLOUDFLARE_API_TOKEN
5. Try publish again - should succeed

**Step 3: Manual DNS verification**

If using real Cloudflare credentials:

1. Log into Cloudflare dashboard
2. Check DNS records for acdev3.com
3. Verify A record was created for test subdomain
4. Confirm IP address matches SERVER_IP_ADDRESS
5. Confirm "Proxied" is enabled (orange cloud)

**Step 4: Test ISR revalidation**

1. Publish a dealer
2. Update dealer info in database
3. Call `/api/revalidate` endpoint with dealer path
4. Verify page updates with new content

**Step 5: All tests pass**

Run: `npm test`

Expected: All tests pass including cloudflare-api, isr-revalidation, publish endpoint.

**Step 6: Commit successful full integration**

```bash
git add .
git commit -m "feat: complete Cloudflare subdomain automation integration"
```

---

## Task 11: Production Deployment Checklist

**Files:**

- Create: `docs/CLOUDFLARE_PRODUCTION_DEPLOYMENT.md`

**Step 1: Create deployment guide**

Create `docs/CLOUDFLARE_PRODUCTION_DEPLOYMENT.md`:

````markdown
# Cloudflare Production Deployment

## Pre-Deployment Checklist

- [ ] All tests pass in staging environment
- [ ] DNS records verified in Cloudflare dashboard
- [ ] Rollback scenario tested
- [ ] Error monitoring configured
- [ ] Team notified of deployment

## Production Deployment Steps

### 1. Update Production Environment Variables

```bash
# Production values
CLOUDFLARE_API_TOKEN=<production_token>
CLOUDFLARE_ZONE_ID=<production_zone>
SERVER_IP_ADDRESS=<production_ip>
```
````

### 2. Deploy Code

```bash
git push origin feature/cloudflare-dns
# Create PR and merge to main
npm run build
npm run start
```

### 3. Monitor First Publishes

- Watch for API errors in logs
- Check Cloudflare dashboard for new records
- Verify DNS propagation (30-60 seconds)
- Monitor dealer page accessibility

### 4. Rollback Plan

If issues occur:

```bash
# Option 1: Revert code change
git revert <commit-hash>

# Option 2: Manually delete DNS records via Cloudflare dashboard
```

## Monitoring

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

````

**Step 2: Commit**

```bash
git add docs/CLOUDFLARE_PRODUCTION_DEPLOYMENT.md
git commit -m "docs: add production deployment guide for Cloudflare integration"
````

---

## Summary of Changes

| Component        | Files                                                                  | Status        |
| ---------------- | ---------------------------------------------------------------------- | ------------- |
| Database         | `prisma/schema.prisma`                                                 | Add fields    |
| API Wrapper      | `lib/cloudflare-api.ts`                                                | Create        |
| ISR Helper       | `lib/isr-revalidation.ts`                                              | Create        |
| Middleware       | `middleware.ts`                                                        | Create/Update |
| Dealer Page      | `app/dealers/[subdomain]/page.tsx`                                     | Create        |
| Revalidate API   | `app/api/revalidate/route.ts`                                          | Create        |
| Publish Endpoint | `app/api/dealers/[id]/publish/route.ts`                                | Update        |
| Tests            | `lib/__tests__/*`, `app/api/__tests__/*`                               | Create        |
| Documentation    | `docs/CLOUDFLARE_SETUP.md`, `docs/CLOUDFLARE_PRODUCTION_DEPLOYMENT.md` | Create        |

---

## Testing Overview

**Unit Tests:**

- Cloudflare API: create, delete, error handling
- ISR revalidation: trigger, error handling
- DNS record idempotency

**Integration Tests:**

- Publish endpoint: full flow, rollback, error scenarios
- Middleware: subdomain extraction
- Dealer page: static generation, ISR revalidation

**Manual Testing:**

- Full registration → publish → DNS creation → subdomain access
- Error scenarios and rollbacks
- DNS propagation verification
- ISR revalidation verification

---

Plan complete and saved to `docs/plans/2025-01-19-cloudflare-subdomain-automation-implementation.md`.

Two execution options:

**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration

**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints

Which approach would you prefer?
