feat(hostinger): VPS actions, DNS editing, domain lock/privacy, nameservers, auto-renewal

This commit is contained in:
Lizandro Guarnizo
2026-05-14 20:42:57 -05:00
parent abecb9b305
commit 8343101525
4 changed files with 747 additions and 6 deletions
+161
View File
@@ -1,6 +1,7 @@
package services
import (
"bytes"
"context"
"encoding/json"
"fmt"
@@ -54,6 +55,37 @@ func (c *HostingerClient) getRaw(ctx context.Context, path string) ([]byte, erro
return body, nil
}
// writeRaw envía una petición HTTP autenticada con método y body opcionales.
func (c *HostingerClient) writeRaw(ctx context.Context, method, path string, payload interface{}) ([]byte, error) {
var reqBody io.Reader
if payload != nil {
b, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("hostinger: serializar body: %w", err)
}
reqBody = bytes.NewReader(b)
}
req, err := http.NewRequestWithContext(ctx, method, hostingerBaseURL+path, reqBody)
if err != nil {
return nil, fmt.Errorf("hostinger: crear request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("hostinger: ejecutar request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("hostinger: leer body: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("hostinger: status %d %s", resp.StatusCode, string(body))
}
return body, nil
}
// get realiza una petición GET autenticada y decodifica el cuerpo en dest.
func (c *HostingerClient) get(path string, dest interface{}) error {
body, err := c.getRaw(context.Background(), path)
@@ -115,6 +147,19 @@ type HostingerNameServers struct {
NS4 string `json:"ns4"`
}
// HostingerDNSZoneInput es un grupo de registros para PUT /dns/v1/zones/{domain}.
type HostingerDNSZoneInput struct {
Name string `json:"name"`
Type string `json:"type"`
TTL int `json:"ttl,omitempty"`
Records []HostingerDNSZoneValue `json:"records"`
}
// HostingerDNSZoneValue es el valor de contenido de un registro DNS.
type HostingerDNSZoneValue struct {
Content string `json:"content"`
}
// HostingerSubscription representa una suscripción de facturación.
type HostingerSubscription struct {
ID string `json:"id"`
@@ -263,6 +308,122 @@ func (c *HostingerClient) GetOrders() ([]HostingerSubscription, error) {
return arr, nil
}
// ─── DNS: escritura ─────────────────────────────────────────────────────────
// UpdateDNSZone agrega/actualiza registros en la zona DNS.
// overwrite=true reemplaza registros del mismo tipo/nombre; false los agrega.
func (c *HostingerClient) UpdateDNSZone(domain string, overwrite bool, zone []HostingerDNSZoneInput) error {
path := fmt.Sprintf("/dns/v1/zones/%s", domain)
_, err := c.writeRaw(context.Background(), http.MethodPut, path,
map[string]interface{}{"overwrite": overwrite, "zone": zone})
return err
}
// ResetDNS restaura la zona DNS de un dominio a sus valores por defecto.
func (c *HostingerClient) ResetDNS(domain string) error {
path := fmt.Sprintf("/dns/v1/zones/%s/reset", domain)
_, err := c.writeRaw(context.Background(), http.MethodPost, path, map[string]bool{"sync": true})
return err
}
// ─── Domains: escritura ──────────────────────────────────────────────────────
// UpdateNameservers actualiza los nameservers de un dominio.
func (c *HostingerClient) UpdateNameservers(domain, ns1, ns2, ns3, ns4 string) error {
path := fmt.Sprintf("/domains/v1/portfolio/%s/nameservers", domain)
_, err := c.writeRaw(context.Background(), http.MethodPut, path,
map[string]string{"ns1": ns1, "ns2": ns2, "ns3": ns3, "ns4": ns4})
return err
}
// SetDomainLock activa (PUT) o desactiva (DELETE) el bloqueo del dominio.
func (c *HostingerClient) SetDomainLock(domain string, enable bool) error {
path := fmt.Sprintf("/domains/v1/portfolio/%s/domain-lock", domain)
method := http.MethodPut
if !enable {
method = http.MethodDelete
}
_, err := c.writeRaw(context.Background(), method, path, nil)
return err
}
// SetPrivacyProtection activa (PUT) o desactiva (DELETE) la protección WHOIS.
func (c *HostingerClient) SetPrivacyProtection(domain string, enable bool) error {
path := fmt.Sprintf("/domains/v1/portfolio/%s/privacy-protection", domain)
method := http.MethodPut
if !enable {
method = http.MethodDelete
}
_, err := c.writeRaw(context.Background(), method, path, nil)
return err
}
// ─── VPS: acciones ───────────────────────────────────────────────────────────
// VPSAction ejecuta una acción en una VM: start | stop | restart.
func (c *HostingerClient) VPSAction(vmID int, action string) error {
path := fmt.Sprintf("/vps/v1/virtual-machines/%d/%s", vmID, action)
_, err := c.writeRaw(context.Background(), http.MethodPost, path, nil)
return err
}
// SetVPSRootPassword cambia la contraseña root de una VM.
func (c *HostingerClient) SetVPSRootPassword(vmID int, password string) error {
path := fmt.Sprintf("/vps/v1/virtual-machines/%d/root-password", vmID)
_, err := c.writeRaw(context.Background(), http.MethodPut, path, map[string]string{"password": password})
return err
}
// SetVPSHostname cambia el hostname de una VM.
func (c *HostingerClient) SetVPSHostname(vmID int, hostname string) error {
path := fmt.Sprintf("/vps/v1/virtual-machines/%d/hostname", vmID)
_, err := c.writeRaw(context.Background(), http.MethodPut, path, map[string]string{"hostname": hostname})
return err
}
// GetVPSMetrics obtiene las métricas de uso de una VM.
func (c *HostingerClient) GetVPSMetrics(vmID int) (json.RawMessage, error) {
path := fmt.Sprintf("/vps/v1/virtual-machines/%d/metrics", vmID)
body, err := c.getRaw(context.Background(), path)
if err != nil {
return nil, err
}
return json.RawMessage(body), nil
}
// GetVPSBackups obtiene los backups de una VM.
func (c *HostingerClient) GetVPSBackups(vmID int) (json.RawMessage, error) {
path := fmt.Sprintf("/vps/v1/virtual-machines/%d/backups", vmID)
body, err := c.getRaw(context.Background(), path)
if err != nil {
return nil, err
}
return json.RawMessage(body), nil
}
// ─── Billing: acciones ───────────────────────────────────────────────────────
// ToggleAutoRenewal activa (true) o desactiva la autorenovación de una suscripción.
func (c *HostingerClient) ToggleAutoRenewal(subscriptionID string, enable bool) (*HostingerSubscription, error) {
var path, method string
if enable {
path = fmt.Sprintf("/billing/v1/subscriptions/%s/auto-renewal/enable", subscriptionID)
method = http.MethodPatch
} else {
path = fmt.Sprintf("/billing/v1/subscriptions/%s/auto-renewal/disable", subscriptionID)
method = http.MethodDelete
}
body, err := c.writeRaw(context.Background(), method, path, nil)
if err != nil {
return nil, err
}
var sub HostingerSubscription
if err := json.Unmarshal(body, &sub); err != nil {
return nil, fmt.Errorf("hostinger: no se pudo interpretar respuesta: %w", err)
}
return &sub, nil
}
// GetHostingAccounts obtiene las cuentas de hosting.
// Endpoint: GET /hosting/v1/websites
func (c *HostingerClient) GetHostingAccounts() ([]HostingerHosting, error) {