package cloudflare import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "time" ) const ( baseURL = "https://api.cloudflare.com/client/v4" ) // Client handles Cloudflare API interactions type Client struct { apiToken string zoneID string http *http.Client } // NewClient creates a new Cloudflare API client func NewClient() *Client { return &Client{ apiToken: os.Getenv("CLOUDFLARE_API_TOKEN"), zoneID: os.Getenv("CLOUDFLARE_ZONE_ID"), http: &http.Client{ Timeout: 30 * time.Second, }, } } // NewClientWithConfig creates a client with custom config func NewClientWithConfig(apiToken, zoneID string) *Client { return &Client{ apiToken: apiToken, zoneID: zoneID, http: &http.Client{ Timeout: 30 * time.Second, }, } } // Zone represents a Cloudflare zone type Zone struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` } // ZonesResponse is the response from /zones endpoint type ZonesResponse struct { Success bool `json:"success"` Errors []Error `json:"errors"` Messages []string `json:"messages"` Result []Zone `json:"result"` } // PurgeResponse is the response from cache purge endpoints type PurgeResponse struct { Success bool `json:"success"` Errors []Error `json:"errors"` Messages []string `json:"messages"` Result struct { ID string `json:"id"` } `json:"result"` } // Error represents a Cloudflare API error type Error struct { Code int `json:"code"` Message string `json:"message"` } // doRequest executes an HTTP request to Cloudflare API func (c *Client) doRequest(method, endpoint string, body interface{}) ([]byte, error) { var reqBody io.Reader if body != nil { jsonBody, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("failed to marshal request body: %w", err) } reqBody = bytes.NewBuffer(jsonBody) } req, err := http.NewRequest(method, baseURL+endpoint, reqBody) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.apiToken) req.Header.Set("Content-Type", "application/json") resp, err := c.http.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode >= 400 { return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody)) } return respBody, nil } // GetZones returns all zones for the account func (c *Client) GetZones() ([]Zone, error) { respBody, err := c.doRequest("GET", "/zones", nil) if err != nil { return nil, err } var response ZonesResponse if err := json.Unmarshal(respBody, &response); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } if !response.Success { if len(response.Errors) > 0 { return nil, fmt.Errorf("API error: %s", response.Errors[0].Message) } return nil, fmt.Errorf("unknown API error") } return response.Result, nil } // PurgeAll purges all cached content for the zone func (c *Client) PurgeAll() (*PurgeResponse, error) { endpoint := fmt.Sprintf("/zones/%s/purge_cache", c.zoneID) body := map[string]bool{"purge_everything": true} respBody, err := c.doRequest("POST", endpoint, body) if err != nil { return nil, err } var response PurgeResponse if err := json.Unmarshal(respBody, &response); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &response, nil } // PurgeByURLs purges specific URLs from cache func (c *Client) PurgeByURLs(urls []string) (*PurgeResponse, error) { endpoint := fmt.Sprintf("/zones/%s/purge_cache", c.zoneID) body := map[string][]string{"files": urls} respBody, err := c.doRequest("POST", endpoint, body) if err != nil { return nil, err } var response PurgeResponse if err := json.Unmarshal(respBody, &response); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &response, nil } // PurgeByTags purges content by cache tags (Enterprise only) func (c *Client) PurgeByTags(tags []string) (*PurgeResponse, error) { endpoint := fmt.Sprintf("/zones/%s/purge_cache", c.zoneID) body := map[string][]string{"tags": tags} respBody, err := c.doRequest("POST", endpoint, body) if err != nil { return nil, err } var response PurgeResponse if err := json.Unmarshal(respBody, &response); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &response, nil } // PurgeByHosts purges content by hostnames func (c *Client) PurgeByHosts(hosts []string) (*PurgeResponse, error) { endpoint := fmt.Sprintf("/zones/%s/purge_cache", c.zoneID) body := map[string][]string{"hosts": hosts} respBody, err := c.doRequest("POST", endpoint, body) if err != nil { return nil, err } var response PurgeResponse if err := json.Unmarshal(respBody, &response); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &response, nil }