# Phase 2: Cloudflare DNS Client

**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend

## Objective
Create an `internal/dns` package with a `Provider` interface and a Cloudflare implementation
using the Cloudflare API v4 (direct HTTP, no SDK).

## Tasks

- [ ] Task 1: Define `Provider` interface in `internal/dns/provider.go`
  - `EnsureRecord(ctx, fqdn, ip) error` — create or update A record
  - `DeleteRecord(ctx, fqdn) error` — delete A record if exists
  - `ListRecords(ctx) ([]Record, error)` — list all A records in the zone
  - `Record` struct: ID, FQDN, Type, Content (IP), Proxied, TTL
- [ ] Task 2: Create `internal/dns/cloudflare.go` — Cloudflare implementation
  - HTTP client with `Authorization: Bearer <token>` header
  - Base URL: `https://api.cloudflare.com/client/v4`
  - `EnsureRecord`: GET records by name, create if missing, update if IP differs
  - `DeleteRecord`: GET record by name, DELETE if found
  - `ListRecords`: GET all A records in zone
  - `ListZones`: GET zones for the token (for zone picker)
  - `TestConnection`: verify token works (GET /user/tokens/verify)
- [ ] Task 3: Create `internal/dns/dns.go` — factory function
  - `NewProvider(providerName, config) (Provider, error)`
  - Config struct with token, zoneID
  - Returns `nil, nil` when providerName is empty (wildcard mode)
- [ ] Task 4: Wire DNS test/zones endpoints in `internal/api/settings.go`
  - `POST /api/settings/dns/test` — create temp Cloudflare client, call TestConnection
  - `GET /api/settings/dns/zones` — create temp client, call ListZones

## Files to Modify/Create
- `internal/dns/provider.go` — interface + Record type
- `internal/dns/cloudflare.go` — Cloudflare implementation
- `internal/dns/dns.go` — factory function
- `internal/api/settings.go` — wire test/zones endpoints to real client

## Acceptance Criteria
- Provider interface defined with EnsureRecord, DeleteRecord, ListRecords
- Cloudflare client makes correct API calls with proper auth headers
- EnsureRecord is idempotent (create if missing, update if changed, no-op if same)
- DeleteRecord is idempotent (no error if record doesn't exist)
- ListZones returns zone ID + name pairs
- TestConnection returns success/failure

## Notes
- Cloudflare API v4 docs: zones endpoint, dns_records endpoint
- Use `context.Context` for timeout control on all HTTP calls
- A records only (type "A"), TTL=1 (auto), proxied=false (DNS only, not CF proxy)

## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)

## Handoff to Next Phase
<!-- Filled in after completion -->
