feat: integración Hostinger + Cloudflare API (modelo, servicio, controlador, rutas, migración)

This commit is contained in:
Lizandro Guarnizo
2026-04-30 23:23:34 -05:00
parent ee1391db81
commit cb8e15ec4c
8 changed files with 829 additions and 0 deletions
+3
View File
@@ -50,6 +50,9 @@ func Migrate() {
&models.Contrato{},
&models.NotificacionLog{},
&models.SmtpConfig{},
// Integraciones externas
&models.HostingerConfig{},
&models.CloudflareConfig{},
); err != nil {
log.Fatalf("Error during main migration: %v", err)
}
+62
View File
@@ -0,0 +1,62 @@
package models
import (
"github.com/sujit-baniya/fiber-boilerplate/app"
"gorm.io/gorm"
)
// CloudflareConfig almacena el API Token de Cloudflare.
// Solo un registro puede estar activo a la vez.
type CloudflareConfig struct {
gorm.Model
// API Token (recomendado) se usa como Bearer token
APIToken string `json:"api_token" gorm:"column:api_token;type:text;not null"`
// Account ID principal (opcional, para endpoints de cuenta)
AccountID string `json:"account_id" gorm:"column:account_id;type:text"`
Activo bool `json:"activo" gorm:"column:activo;default:true"`
Nota string `json:"nota" gorm:"column:nota;type:text"`
}
func (CloudflareConfig) TableName() string { return "cloudflare_config" }
// GetCloudflareConfig obtiene la configuración activa.
func GetCloudflareConfig() (*CloudflareConfig, error) {
var item CloudflareConfig
if err := app.Http.Database.DB.Where("activo = ?", true).Order("id DESC").First(&item).Error; err != nil {
return nil, err
}
return &item, nil
}
// SaveCloudflareConfig desactiva la config previa y guarda la nueva.
func SaveCloudflareConfig(s CloudflareConfig) error {
app.Http.Database.DB.Model(&CloudflareConfig{}).Where("activo = ?", true).
Update("activo", false)
s.Activo = true
if s.ID > 0 {
return app.Http.Database.DB.Model(&s).Updates(map[string]interface{}{
"api_token": s.APIToken,
"account_id": s.AccountID,
"nota": s.Nota,
"activo": true,
}).Error
}
return app.Http.Database.DB.Create(&s).Error
}
// GetAllCloudflareConfig lista todos los registros con paginación.
func GetAllCloudflareConfig(limit, offset int) ([]CloudflareConfig, int64, error) {
var items []CloudflareConfig
var total int64
db := app.Http.Database.DB.Model(&CloudflareConfig{})
db.Count(&total)
if err := db.Order("id DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
// DeleteCloudflareConfig elimina un registro.
func DeleteCloudflareConfig(id uint) error {
return app.Http.Database.DB.Delete(&CloudflareConfig{}, id).Error
}
+58
View File
@@ -0,0 +1,58 @@
package models
import (
"github.com/sujit-baniya/fiber-boilerplate/app"
"gorm.io/gorm"
)
// HostingerConfig almacena el token de acceso a la API de Hostinger.
// Solo un registro puede estar activo a la vez.
type HostingerConfig struct {
gorm.Model
Token string `json:"token" gorm:"column:token;type:text;not null"` // Bearer token de Hostinger
Activo bool `json:"activo" gorm:"column:activo;default:true"`
Nota string `json:"nota" gorm:"column:nota;type:text"`
}
func (HostingerConfig) TableName() string { return "hostinger_config" }
// GetHostingerConfig obtiene la configuración activa.
func GetHostingerConfig() (*HostingerConfig, error) {
var item HostingerConfig
if err := app.Http.Database.DB.Where("activo = ?", true).Order("id DESC").First(&item).Error; err != nil {
return nil, err
}
return &item, nil
}
// SaveHostingerConfig desactiva la config previa y guarda la nueva.
func SaveHostingerConfig(s HostingerConfig) error {
app.Http.Database.DB.Model(&HostingerConfig{}).Where("activo = ?", true).
Update("activo", false)
s.Activo = true
if s.ID > 0 {
return app.Http.Database.DB.Model(&s).Updates(map[string]interface{}{
"token": s.Token,
"nota": s.Nota,
"activo": true,
}).Error
}
return app.Http.Database.DB.Create(&s).Error
}
// GetAllHostingerConfig lista todos los registros con paginación.
func GetAllHostingerConfig(limit, offset int) ([]HostingerConfig, int64, error) {
var items []HostingerConfig
var total int64
db := app.Http.Database.DB.Model(&HostingerConfig{})
db.Count(&total)
if err := db.Order("id DESC").Limit(limit).Offset(offset).Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
// DeleteHostingerConfig elimina un registro.
func DeleteHostingerConfig(id uint) error {
return app.Http.Database.DB.Delete(&HostingerConfig{}, id).Error
}
+214
View File
@@ -0,0 +1,214 @@
package services
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const cloudflareBaseURL = "https://api.cloudflare.com/client/v4"
// CloudflareClient es el cliente HTTP para la API de Cloudflare.
type CloudflareClient struct {
token string
accountID string
httpClient *http.Client
}
// NewCloudflareClient crea un cliente con el API Token.
func NewCloudflareClient(token, accountID string) *CloudflareClient {
return &CloudflareClient{
token: token,
accountID: accountID,
httpClient: &http.Client{
Timeout: 20 * time.Second,
},
}
}
// get realiza una petición GET autenticada y devuelve el body.
func (c *CloudflareClient) get(path string, dest interface{}) error {
req, err := http.NewRequest(http.MethodGet, cloudflareBaseURL+path, nil)
if err != nil {
return fmt.Errorf("cloudflare: 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 fmt.Errorf("cloudflare: ejecutar request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("cloudflare: leer body: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("cloudflare: status %d %s", resp.StatusCode, string(body))
}
// Cloudflare siempre envuelve en {"result":..., "success":true}
var wrapper struct {
Success bool `json:"success"`
Errors []CFError `json:"errors"`
Result json.RawMessage `json:"result"`
}
if err := json.Unmarshal(body, &wrapper); err != nil {
return fmt.Errorf("cloudflare: decodificar envelope: %w", err)
}
if !wrapper.Success {
if len(wrapper.Errors) > 0 {
return fmt.Errorf("cloudflare API error %d: %s", wrapper.Errors[0].Code, wrapper.Errors[0].Message)
}
return fmt.Errorf("cloudflare: respuesta no exitosa")
}
if dest != nil && wrapper.Result != nil {
if err := json.Unmarshal(wrapper.Result, dest); err != nil {
return fmt.Errorf("cloudflare: decodificar result: %w", err)
}
}
return nil
}
// ─── Tipos de error ──────────────────────────────────────────────────────────
type CFError struct {
Code int `json:"code"`
Message string `json:"message"`
}
// ─── Tipos de respuesta ──────────────────────────────────────────────────────
// CFUser información del usuario autenticado.
type CFUser struct {
ID string `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Username string `json:"username"`
}
// CFZone representa una zona (dominio) en Cloudflare.
type CFZone struct {
ID string `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
Paused bool `json:"paused"`
Type string `json:"type"`
NameServers []string `json:"name_servers"`
OriginalNS []string `json:"original_name_servers"`
ModifiedOn string `json:"modified_on"`
ActivatedOn string `json:"activated_on"`
Plan CFPlan `json:"plan"`
}
// CFPlan representa el plan de una zona.
type CFPlan struct {
ID string `json:"id"`
Name string `json:"name"`
}
// CFDNSRecord representa un registro DNS.
type CFDNSRecord struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
Proxied bool `json:"proxied"`
Proxiable bool `json:"proxiable"`
TTL int `json:"ttl"`
Priority int `json:"priority,omitempty"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
}
// CFSSLStatus representa el estado SSL de una zona.
type CFSSLStatus struct {
ID string `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
Hosts []string `json:"hosts"`
PrimaryCert string `json:"primary_certificate"`
ExpiresOn string `json:"expires_on"`
}
// CFFirewallRule representa una regla de firewall (Access Rules).
type CFFirewallRule struct {
ID string `json:"id"`
Notes string `json:"notes"`
AllowedModes []string `json:"allowed_modes"`
Mode string `json:"mode"`
Configuration CFFirewallCfg `json:"configuration"`
Scope CFScope `json:"scope"`
CreatedOn string `json:"created_on"`
ModifiedOn string `json:"modified_on"`
}
// CFFirewallCfg configuración del target en la regla.
type CFFirewallCfg struct {
Target string `json:"target"`
Value string `json:"value"`
}
// CFScope scope de la regla (zone o account).
type CFScope struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
}
// ─── Métodos de la API ───────────────────────────────────────────────────────
// GetUser obtiene la información del usuario autenticado.
func (c *CloudflareClient) GetUser() (*CFUser, error) {
var user CFUser
if err := c.get("/user", &user); err != nil {
return nil, err
}
return &user, nil
}
// GetZones lista todas las zonas de la cuenta.
func (c *CloudflareClient) GetZones() ([]CFZone, error) {
var zones []CFZone
// pagina 1, 50 por página (máximo)
if err := c.get("/zones?per_page=50&page=1", &zones); err != nil {
return nil, err
}
return zones, nil
}
// GetDNSRecords devuelve los registros DNS de una zona.
func (c *CloudflareClient) GetDNSRecords(zoneID string) ([]CFDNSRecord, error) {
var records []CFDNSRecord
path := fmt.Sprintf("/zones/%s/dns_records?per_page=100&page=1", zoneID)
if err := c.get(path, &records); err != nil {
return nil, err
}
return records, nil
}
// GetSSLCertificates devuelve los certificate packs de una zona.
func (c *CloudflareClient) GetSSLCertificates(zoneID string) ([]CFSSLStatus, error) {
var certs []CFSSLStatus
path := fmt.Sprintf("/zones/%s/ssl/certificate_packs", zoneID)
if err := c.get(path, &certs); err != nil {
return nil, err
}
return certs, nil
}
// GetFirewallRules devuelve las reglas de acceso IP de una zona.
func (c *CloudflareClient) GetFirewallRules(zoneID string) ([]CFFirewallRule, error) {
var rules []CFFirewallRule
path := fmt.Sprintf("/zones/%s/firewall/access_rules/rules?per_page=50&page=1", zoneID)
if err := c.get(path, &rules); err != nil {
return nil, err
}
return rules, nil
}
+192
View File
@@ -0,0 +1,192 @@
package services
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const hostingerBaseURL = "https://developers.hostinger.com/api"
// HostingerClient es el cliente HTTP para la API de Hostinger.
type HostingerClient struct {
token string
httpClient *http.Client
}
// NewHostingerClient crea un cliente con el token Bearer.
func NewHostingerClient(token string) *HostingerClient {
return &HostingerClient{
token: token,
httpClient: &http.Client{
Timeout: 20 * time.Second,
},
}
}
// get realiza una petición GET autenticada y decodifica el cuerpo en dest.
func (c *HostingerClient) get(path string, dest interface{}) error {
req, err := http.NewRequest(http.MethodGet, hostingerBaseURL+path, nil)
if err != nil {
return 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 fmt.Errorf("hostinger: ejecutar request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("hostinger: leer body: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("hostinger: status %d %s", resp.StatusCode, string(body))
}
if dest != nil {
if err := json.Unmarshal(body, dest); err != nil {
return fmt.Errorf("hostinger: decodificar respuesta: %w", err)
}
}
return nil
}
// ─── Tipos de respuesta ──────────────────────────────────────────────────────
// HostingerVPS representa una máquina virtual VPS.
type HostingerVPS struct {
ID int `json:"id"`
Hostname string `json:"hostname"`
State string `json:"state"`
Plan string `json:"plan"`
DataCenter string `json:"data_center"`
CPU int `json:"cpus"`
RAMBytes int64 `json:"ram"`
DiskBytes int64 `json:"disk"`
IPV4 string `json:"ipv4"`
IPV6 string `json:"ipv6"`
}
// HostingerDomain representa un dominio en el portafolio.
type HostingerDomain struct {
Domain string `json:"domain"`
Status string `json:"status"`
ExpiresAt string `json:"expires_at"`
RegisteredAt string `json:"registered_at"`
}
// HostingerDNSRecord representa un registro DNS.
type HostingerDNSRecord struct {
ID int `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
TTL int `json:"ttl"`
Priority int `json:"priority,omitempty"`
}
// HostingerOrder representa una orden de facturación.
type HostingerOrder struct {
ID int `json:"id"`
Status string `json:"status"`
Total float64 `json:"total"`
Currency string `json:"currency"`
CreatedAt string `json:"created_at"`
}
// HostingerHosting representa una cuenta de hosting.
type HostingerHosting struct {
ID int `json:"id"`
Domain string `json:"domain"`
Plan string `json:"plan"`
State string `json:"state"`
ExpiresAt string `json:"expires_at"`
DiskUsed int64 `json:"disk_used"`
DiskLimit int64 `json:"disk_limit"`
}
// ─── Métodos de la API ───────────────────────────────────────────────────────
// GetVPSList obtiene la lista de VPS/VMs.
func (c *HostingerClient) GetVPSList() ([]HostingerVPS, error) {
var result struct {
Data []HostingerVPS `json:"data"`
}
if err := c.get("/vps/v1/virtual-machines", &result); err != nil {
// intenta respuesta array directa
var arr []HostingerVPS
if err2 := c.get("/vps/v1/virtual-machines", &arr); err2 != nil {
return nil, err
}
return arr, nil
}
return result.Data, nil
}
// GetDomains obtiene el portafolio de dominios.
func (c *HostingerClient) GetDomains() ([]HostingerDomain, error) {
var result struct {
Data []HostingerDomain `json:"data"`
}
if err := c.get("/domains/v1/portfolio", &result); err != nil {
var arr []HostingerDomain
if err2 := c.get("/domains/v1/portfolio", &arr); err2 != nil {
return nil, err
}
return arr, nil
}
return result.Data, nil
}
// GetDNSRecords obtiene los registros DNS de un dominio.
func (c *HostingerClient) GetDNSRecords(domain string) ([]HostingerDNSRecord, error) {
var result struct {
Data []HostingerDNSRecord `json:"data"`
}
path := fmt.Sprintf("/dns/v1/zones/%s/records", domain)
if err := c.get(path, &result); err != nil {
var arr []HostingerDNSRecord
if err2 := c.get(path, &arr); err2 != nil {
return nil, err
}
return arr, nil
}
return result.Data, nil
}
// GetOrders obtiene las órdenes de facturación.
func (c *HostingerClient) GetOrders() ([]HostingerOrder, error) {
var result struct {
Data []HostingerOrder `json:"data"`
}
if err := c.get("/billing/v1/orders", &result); err != nil {
var arr []HostingerOrder
if err2 := c.get("/billing/v1/orders", &arr); err2 != nil {
return nil, err
}
return arr, nil
}
return result.Data, nil
}
// GetHostingAccounts obtiene las cuentas de hosting.
func (c *HostingerClient) GetHostingAccounts() ([]HostingerHosting, error) {
var result struct {
Data []HostingerHosting `json:"data"`
}
if err := c.get("/hosting/v1/accounts", &result); err != nil {
var arr []HostingerHosting
if err2 := c.get("/hosting/v1/accounts", &arr); err2 != nil {
return nil, err
}
return arr, nil
}
return result.Data, nil
}
+146
View File
@@ -0,0 +1,146 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
// ─── Configuración ────────────────────────────────────────────────────────────
// CloudflareConfigPage renderiza la vista de gestión de Cloudflare.
func CloudflareConfigPage(c *fiber.Ctx) error {
cfg, _ := models.GetCloudflareConfig()
return c.Render("cloudflare", fiber.Map{
"Title": "Cloudflare API",
"Config": cfg,
})
}
// SaveCloudflareConfig guarda o actualiza el API token de Cloudflare.
func SaveCloudflareConfig(c *fiber.Ctx) error {
type body struct {
ID uint `json:"id" form:"id"`
APIToken string `json:"api_token" form:"api_token"`
AccountID string `json:"account_id" form:"account_id"`
Nota string `json:"nota" form:"nota"`
}
var b body
if err := c.BodyParser(&b); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
}
if b.APIToken == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "api_token requerido"})
}
cfg := models.CloudflareConfig{
APIToken: b.APIToken,
AccountID: b.AccountID,
Nota: b.Nota,
}
cfg.ID = b.ID
if err := models.SaveCloudflareConfig(cfg); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"message": "Configuración guardada"})
}
// ─── Helper ───────────────────────────────────────────────────────────────────
func cloudflareClient() (*services.CloudflareClient, error) {
cfg, err := models.GetCloudflareConfig()
if err != nil {
return nil, err
}
return services.NewCloudflareClient(cfg.APIToken, cfg.AccountID), nil
}
// ─── Endpoints de datos ───────────────────────────────────────────────────────
// GetCloudflareUser devuelve la información del usuario autenticado en Cloudflare.
func GetCloudflareUser(c *fiber.Ctx) error {
client, err := cloudflareClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Cloudflare",
})
}
data, err := client.GetUser()
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data})
}
// GetCloudflareZones lista todas las zonas (dominios) de la cuenta.
func GetCloudflareZones(c *fiber.Ctx) error {
client, err := cloudflareClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Cloudflare",
})
}
data, err := client.GetZones()
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data})
}
// GetCloudflareDNS devuelve los registros DNS de una zona (:zone_id).
func GetCloudflareDNS(c *fiber.Ctx) error {
zoneID := c.Params("zone_id")
if zoneID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id requerido"})
}
client, err := cloudflareClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Cloudflare",
})
}
data, err := client.GetDNSRecords(zoneID)
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data, "zone_id": zoneID})
}
// GetCloudflareSSL devuelve los certificados SSL de una zona (:zone_id).
func GetCloudflareSSL(c *fiber.Ctx) error {
zoneID := c.Params("zone_id")
if zoneID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id requerido"})
}
client, err := cloudflareClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Cloudflare",
})
}
data, err := client.GetSSLCertificates(zoneID)
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data, "zone_id": zoneID})
}
// GetCloudflareFirewall devuelve las reglas de firewall de una zona (:zone_id).
func GetCloudflareFirewall(c *fiber.Ctx) error {
zoneID := c.Params("zone_id")
if zoneID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "zone_id requerido"})
}
client, err := cloudflareClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Cloudflare",
})
}
data, err := client.GetFirewallRules(zoneID)
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data, "zone_id": zoneID})
}
+136
View File
@@ -0,0 +1,136 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
// ─── Configuración ────────────────────────────────────────────────────────────
// HostingerConfigPage renderiza la vista de gestión de Hostinger.
func HostingerConfigPage(c *fiber.Ctx) error {
cfg, _ := models.GetHostingerConfig()
return c.Render("hostinger", fiber.Map{
"Title": "Hostinger API",
"Config": cfg,
})
}
// SaveHostingerConfig guarda o actualiza el token de Hostinger.
func SaveHostingerConfig(c *fiber.Ctx) error {
type body struct {
ID uint `json:"id" form:"id"`
Token string `json:"token" form:"token"`
Nota string `json:"nota" form:"nota"`
}
var b body
if err := c.BodyParser(&b); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
}
if b.Token == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "token requerido"})
}
cfg := models.HostingerConfig{
Nota: b.Nota,
Token: b.Token,
}
cfg.ID = b.ID
if err := models.SaveHostingerConfig(cfg); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"message": "Configuración guardada"})
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
func hostingerClient() (*services.HostingerClient, error) {
cfg, err := models.GetHostingerConfig()
if err != nil {
return nil, err
}
return services.NewHostingerClient(cfg.Token), nil
}
// ─── Endpoints de datos ───────────────────────────────────────────────────────
// GetHostingerVPS devuelve la lista de VPS/VMs.
func GetHostingerVPS(c *fiber.Ctx) error {
client, err := hostingerClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Hostinger",
})
}
data, err := client.GetVPSList()
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data})
}
// GetHostingerDomains devuelve el portafolio de dominios.
func GetHostingerDomains(c *fiber.Ctx) error {
client, err := hostingerClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Hostinger",
})
}
data, err := client.GetDomains()
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data})
}
// GetHostingerDNS devuelve los registros DNS de un dominio (:domain).
func GetHostingerDNS(c *fiber.Ctx) error {
domain := c.Params("domain")
if domain == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "dominio requerido"})
}
client, err := hostingerClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Hostinger",
})
}
data, err := client.GetDNSRecords(domain)
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data, "domain": domain})
}
// GetHostingerOrders devuelve las órdenes de facturación.
func GetHostingerOrders(c *fiber.Ctx) error {
client, err := hostingerClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Hostinger",
})
}
data, err := client.GetOrders()
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data})
}
// GetHostingerHosting devuelve las cuentas de hosting.
func GetHostingerHosting(c *fiber.Ctx) error {
client, err := hostingerClient()
if err != nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "No se encontró configuración activa de Hostinger",
})
}
data, err := client.GetHostingAccounts()
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{"data": data})
}
+18
View File
@@ -98,4 +98,22 @@ func UserRoutes(app fiber.Router) {
// ─── Módulo de Renovaciones / Contratos ──────────────────────────
RenovacionesRoutes(protected)
// ─── Hostinger API ────────────────────────────────────────────────
protected.Get("/hostinger", middlewares.MenuMiddleware, controllers.HostingerConfigPage)
protected.Post("/hostinger/config", controllers.SaveHostingerConfig)
protected.Get("/hostinger/vps", controllers.GetHostingerVPS)
protected.Get("/hostinger/domains", controllers.GetHostingerDomains)
protected.Get("/hostinger/dns/:domain", controllers.GetHostingerDNS)
protected.Get("/hostinger/orders", controllers.GetHostingerOrders)
protected.Get("/hostinger/hosting", controllers.GetHostingerHosting)
// ─── Cloudflare API ───────────────────────────────────────────────
protected.Get("/cloudflare", middlewares.MenuMiddleware, controllers.CloudflareConfigPage)
protected.Post("/cloudflare/config", controllers.SaveCloudflareConfig)
protected.Get("/cloudflare/user", controllers.GetCloudflareUser)
protected.Get("/cloudflare/zones", controllers.GetCloudflareZones)
protected.Get("/cloudflare/zones/:zone_id/dns", controllers.GetCloudflareDNS)
protected.Get("/cloudflare/zones/:zone_id/ssl", controllers.GetCloudflareSSL)
protected.Get("/cloudflare/zones/:zone_id/firewall", controllers.GetCloudflareFirewall)
}