Cambia StatusBadGateway (502) → StatusBadRequest (400) en todos los errores del proxy Coolify. Cloudflare intercepta respuestas 502 del origen y muestra su propia pantalla, ocultando el error al frontend. Con 400, el JSON de error llega al cliente y Alpine.js puede mostrarlo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
259 lines
8.8 KiB
Go
259 lines
8.8 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
|
|
)
|
|
|
|
func coolifyDo(method, endpoint string, reqBody io.Reader, contentType string, cfg *models.CoolifyConfig) ([]byte, int, error) {
|
|
base := strings.TrimRight(cfg.BaseURL, "/")
|
|
url := fmt.Sprintf("%s/api/v1%s", base, endpoint)
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
req, err := http.NewRequest(method, url, reqBody)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+cfg.ApiToken)
|
|
req.Header.Set("Accept", "application/json")
|
|
if contentType != "" {
|
|
req.Header.Set("Content-Type", contentType)
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024*1024))
|
|
return body, resp.StatusCode, nil
|
|
}
|
|
|
|
func coolifyProxy(c *fiber.Ctx, method, endpoint string) error {
|
|
cfg, err := models.GetCoolifyConfig()
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Configura primero la integración Coolify"})
|
|
}
|
|
if !cfg.Activo {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La integración Coolify está inactiva"})
|
|
}
|
|
ep := endpoint
|
|
if qs := string(c.Request().URI().QueryString()); qs != "" {
|
|
ep = endpoint + "?" + qs
|
|
}
|
|
var bodyReader io.Reader
|
|
ct := ""
|
|
if raw := c.Body(); len(raw) > 0 {
|
|
bodyReader = strings.NewReader(string(raw))
|
|
ct = c.Get("Content-Type", "application/json")
|
|
}
|
|
body, status, err := coolifyDo(method, ep, bodyReader, ct, cfg)
|
|
if err != nil {
|
|
// No devolver 502: Cloudflare lo intercepta y oculta el error al frontend
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "No se pudo conectar a Coolify: " + err.Error()})
|
|
}
|
|
var result json.RawMessage
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
result = json.RawMessage(fmt.Sprintf(`{"raw":%q}`, string(body)))
|
|
}
|
|
// Si Coolify devuelve 5xx, bajar a 400 para que Cloudflare no lo intercepte
|
|
if status >= 500 {
|
|
status = fiber.StatusBadRequest
|
|
}
|
|
c.Status(status)
|
|
return c.JSON(result)
|
|
}
|
|
|
|
func CoolifyIndex(c *fiber.Ctx) error {
|
|
cfg, _ := models.GetCoolifyConfig()
|
|
return c.Render("coolify", fiber.Map{
|
|
"user": c.Locals("user").(map[string]interface{}),
|
|
"modules": c.Locals("userModules"),
|
|
"config": cfg,
|
|
}, "layouts/main")
|
|
}
|
|
|
|
func CoolifyGetConfig(c *fiber.Ctx) error {
|
|
cfg, err := models.GetCoolifyConfig()
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{"config": nil})
|
|
}
|
|
masked := "••••••••"
|
|
if cfg.ApiToken == "" {
|
|
masked = ""
|
|
}
|
|
return c.JSON(fiber.Map{"config": fiber.Map{
|
|
"ID": cfg.ID,
|
|
"nombre": cfg.Nombre,
|
|
"base_url": cfg.BaseURL,
|
|
"api_token": masked,
|
|
"activo": cfg.Activo,
|
|
}})
|
|
}
|
|
|
|
func CoolifySaveConfig(c *fiber.Ctx) error {
|
|
type Req struct {
|
|
Nombre string `json:"nombre"`
|
|
BaseURL string `json:"base_url"`
|
|
ApiToken string `json:"api_token"`
|
|
Activo bool `json:"activo"`
|
|
}
|
|
var req Req
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
|
|
}
|
|
req.BaseURL = strings.TrimRight(strings.TrimSpace(req.BaseURL), "/")
|
|
if req.BaseURL == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "base_url es requerido"})
|
|
}
|
|
cfg, err := models.UpsertCoolifyConfig(req.Nombre, req.BaseURL, req.ApiToken, req.Activo)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
return c.JSON(fiber.Map{"ok": true, "config": cfg})
|
|
}
|
|
|
|
func CoolifyTestConnection(c *fiber.Ctx) error {
|
|
cfg, err := models.GetCoolifyConfig()
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Sin config"})
|
|
}
|
|
base := strings.TrimRight(cfg.BaseURL, "/")
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Get(base + "/api/health")
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "No se pudo conectar a Coolify: " + err.Error()})
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
|
|
var result json.RawMessage
|
|
if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
|
|
result = json.RawMessage(fmt.Sprintf(`{"raw":%q}`, string(body)))
|
|
}
|
|
c.Status(resp.StatusCode)
|
|
return c.JSON(result)
|
|
}
|
|
|
|
func CoolifyListApplications(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/applications")
|
|
}
|
|
func CoolifyGetApplication(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid"))
|
|
}
|
|
func CoolifyApplicationLogs(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/logs")
|
|
}
|
|
func CoolifyApplicationEnvs(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/envs")
|
|
}
|
|
func CoolifyApplicationEnvCreate(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodPost, "/applications/"+c.Params("uuid")+"/envs")
|
|
}
|
|
func CoolifyApplicationEnvUpdateBulk(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodPatch, "/applications/"+c.Params("uuid")+"/envs")
|
|
}
|
|
func CoolifyApplicationEnvDelete(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodDelete, "/applications/"+c.Params("uuid")+"/envs/"+c.Params("env_id"))
|
|
}
|
|
func CoolifyApplicationStart(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/start")
|
|
}
|
|
func CoolifyApplicationStop(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/stop")
|
|
}
|
|
func CoolifyApplicationRestart(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/restart")
|
|
}
|
|
func CoolifyApplicationDeployments(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/applications/"+c.Params("uuid")+"/deployments")
|
|
}
|
|
func CoolifyApplicationDeploy(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodPost, "/deploy")
|
|
}
|
|
|
|
func CoolifyListServers(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/servers")
|
|
}
|
|
func CoolifyGetServer(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid"))
|
|
}
|
|
func CoolifyServerResources(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/resources")
|
|
}
|
|
func CoolifyServerDomains(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/domains")
|
|
}
|
|
func CoolifyServerValidate(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/servers/"+c.Params("uuid")+"/validate")
|
|
}
|
|
|
|
func CoolifyListServices(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/services")
|
|
}
|
|
func CoolifyGetService(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid"))
|
|
}
|
|
func CoolifyServiceStart(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/start")
|
|
}
|
|
func CoolifyServiceStop(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/stop")
|
|
}
|
|
func CoolifyServiceRestart(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/restart")
|
|
}
|
|
func CoolifyServiceEnvs(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/services/"+c.Params("uuid")+"/envs")
|
|
}
|
|
func CoolifyServiceEnvUpdateBulk(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodPatch, "/services/"+c.Params("uuid")+"/envs")
|
|
}
|
|
|
|
func CoolifyListDatabases(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/databases")
|
|
}
|
|
func CoolifyGetDatabase(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid"))
|
|
}
|
|
func CoolifyDatabaseStart(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/start")
|
|
}
|
|
func CoolifyDatabaseStop(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/stop")
|
|
}
|
|
func CoolifyDatabaseRestart(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/databases/"+c.Params("uuid")+"/restart")
|
|
}
|
|
|
|
func CoolifyListProjects(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/projects")
|
|
}
|
|
func CoolifyGetProject(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/projects/"+c.Params("uuid"))
|
|
}
|
|
func CoolifyProjectEnvironments(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/projects/"+c.Params("uuid")+"/environments")
|
|
}
|
|
|
|
func CoolifyListDeployments(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/deployments")
|
|
}
|
|
func CoolifyGetDeployment(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/deployments/"+c.Params("uuid"))
|
|
}
|
|
|
|
func CoolifyListTeams(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/teams")
|
|
}
|
|
func CoolifyCurrentTeam(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/teams/current")
|
|
}
|
|
func CoolifyTeamMembers(c *fiber.Ctx) error {
|
|
return coolifyProxy(c, http.MethodGet, "/teams/current/members")
|
|
}
|