gohorsejobs/backend/internal/admin/cloudflare/client.go
Tiago Yamamoto b2284921ea feat: add Cloudflare and cPanel admin routes
Cloudflare Cache Management:
- GET /api/v1/admin/cloudflare/zones
- POST /api/v1/admin/cloudflare/cache/purge-all
- POST /api/v1/admin/cloudflare/cache/purge-urls
- POST /api/v1/admin/cloudflare/cache/purge-tags
- POST /api/v1/admin/cloudflare/cache/purge-hosts

cPanel Email Management:
- GET /api/v1/admin/cpanel/emails
- POST /api/v1/admin/cpanel/emails
- DELETE /api/v1/admin/cpanel/emails/{email}
- PUT /api/v1/admin/cpanel/emails/{email}/password
- PUT /api/v1/admin/cpanel/emails/{email}/quota

All routes protected by JWT auth middleware.
Added CLOUDFLARE_* and CPANEL_* env vars to .env.example
2025-12-14 10:11:36 -03:00

206 lines
5 KiB
Go

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
}