gohorsejobs/backend/internal/admin/cpanel/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

253 lines
6 KiB
Go

package cpanel
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
// Client handles cPanel UAPI interactions
type Client struct {
host string
username string
apiToken string
http *http.Client
}
// NewClient creates a new cPanel API client
func NewClient() *Client {
return &Client{
host: os.Getenv("CPANEL_HOST"),
username: os.Getenv("CPANEL_USERNAME"),
apiToken: os.Getenv("CPANEL_API_TOKEN"),
http: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// NewClientWithConfig creates a client with custom config
func NewClientWithConfig(host, username, apiToken string) *Client {
return &Client{
host: host,
username: username,
apiToken: apiToken,
http: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// EmailAccount represents a cPanel email account
type EmailAccount struct {
Email string `json:"email"`
Login string `json:"login"`
Domain string `json:"domain"`
DiskUsed string `json:"diskused"`
DiskQuota string `json:"diskquota"`
HumandiskUsed string `json:"humandiskused"`
HumandiskQuota string `json:"humandiskquota"`
}
// UAPIResponse is the generic UAPI response structure
type UAPIResponse struct {
APIVersion int `json:"apiversion"`
Func string `json:"func"`
Module string `json:"module"`
Result *json.RawMessage `json:"result"`
Status int `json:"status"`
Errors []string `json:"errors"`
Messages []string `json:"messages"`
}
// ListEmailsResult is the result from list_pops
type ListEmailsResult struct {
Data []EmailAccount `json:"data"`
}
// doRequest executes an HTTP request to cPanel UAPI
func (c *Client) doRequest(module, function string, params map[string]string) ([]byte, error) {
// Build UAPI URL
apiURL := fmt.Sprintf("%s/execute/%s/%s", c.host, module, function)
// Add query parameters
if len(params) > 0 {
values := url.Values{}
for k, v := range params {
values.Set(k, v)
}
apiURL += "?" + values.Encode()
}
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
// cPanel API Token authentication
req.Header.Set("Authorization", fmt.Sprintf("cpanel %s:%s", c.username, c.apiToken))
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
}
// ListEmails returns all email accounts for a domain
func (c *Client) ListEmails(domain string) ([]EmailAccount, error) {
params := map[string]string{}
if domain != "" {
params["domain"] = domain
}
respBody, err := c.doRequest("Email", "list_pops", params)
if err != nil {
return nil, err
}
var response UAPIResponse
if err := json.Unmarshal(respBody, &response); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
if response.Status != 1 {
if len(response.Errors) > 0 {
return nil, fmt.Errorf("API error: %s", response.Errors[0])
}
return nil, fmt.Errorf("unknown API error")
}
var result []EmailAccount
if response.Result != nil {
if err := json.Unmarshal(*response.Result, &result); err != nil {
return nil, fmt.Errorf("failed to parse result: %w", err)
}
}
return result, nil
}
// CreateEmail creates a new email account
func (c *Client) CreateEmail(email, password string, quota int) error {
// Parse email to get user and domain
params := map[string]string{
"email": email,
"password": password,
"quota": fmt.Sprintf("%d", quota), // MB, 0 = unlimited
}
respBody, err := c.doRequest("Email", "add_pop", params)
if err != nil {
return err
}
var response UAPIResponse
if err := json.Unmarshal(respBody, &response); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
if response.Status != 1 {
if len(response.Errors) > 0 {
return fmt.Errorf("API error: %s", response.Errors[0])
}
return fmt.Errorf("failed to create email")
}
return nil
}
// DeleteEmail removes an email account
func (c *Client) DeleteEmail(email string) error {
params := map[string]string{
"email": email,
}
respBody, err := c.doRequest("Email", "delete_pop", params)
if err != nil {
return err
}
var response UAPIResponse
if err := json.Unmarshal(respBody, &response); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
if response.Status != 1 {
if len(response.Errors) > 0 {
return fmt.Errorf("API error: %s", response.Errors[0])
}
return fmt.Errorf("failed to delete email")
}
return nil
}
// ChangePassword changes an email account's password
func (c *Client) ChangePassword(email, newPassword string) error {
params := map[string]string{
"email": email,
"password": newPassword,
}
respBody, err := c.doRequest("Email", "passwd_pop", params)
if err != nil {
return err
}
var response UAPIResponse
if err := json.Unmarshal(respBody, &response); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
if response.Status != 1 {
if len(response.Errors) > 0 {
return fmt.Errorf("API error: %s", response.Errors[0])
}
return fmt.Errorf("failed to change password")
}
return nil
}
// UpdateQuota updates an email account's disk quota
func (c *Client) UpdateQuota(email string, quotaMB int) error {
params := map[string]string{
"email": email,
"quota": fmt.Sprintf("%d", quotaMB),
}
respBody, err := c.doRequest("Email", "edit_pop_quota", params)
if err != nil {
return err
}
var response UAPIResponse
if err := json.Unmarshal(respBody, &response); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
if response.Status != 1 {
if len(response.Errors) > 0 {
return fmt.Errorf("API error: %s", response.Errors[0])
}
return fmt.Errorf("failed to update quota")
}
return nil
}