package controllers import ( "encoding/json" "fmt" "io" "net/http" "strings" "time" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // VcardApiIndex renderiza el panel de integración VCard. func VcardApiIndex(c *fiber.Ctx) error { cfg, _ := models.GetVcardApiConfig() data := fiber.Map{ "user": c.Locals("user").(map[string]interface{}), "modules": c.Locals("userModules"), "config": cfg, } return c.Render("vcard_api", data, "layouts/main") } // VcardApiSaveConfig guarda / actualiza la configuración (base URL + token). func VcardApiSaveConfig(c *fiber.Ctx) error { type Req struct { Nombre string `json:"nombre"` BaseURL string `json:"base_url"` BearerToken string `json:"bearer_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"}) } if strings.TrimSpace(req.BaseURL) == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "base_url es requerido"}) } cfg, err := models.UpsertVcardApiConfig(req.Nombre, strings.TrimRight(strings.TrimSpace(req.BaseURL), "/"), req.BearerToken, 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}) } // VcardApiGetConfig devuelve la configuración actual (sin exponer el token completo). func VcardApiGetConfig(c *fiber.Ctx) error { cfg, err := models.GetVcardApiConfig() if err != nil { return c.JSON(fiber.Map{"config": nil}) } masked := "••••••••" if cfg.BearerToken == "" { masked = "" } return c.JSON(fiber.Map{ "config": fiber.Map{ "ID": cfg.ID, "nombre": cfg.Nombre, "base_url": cfg.BaseURL, "bearer_token": masked, "activo": cfg.Activo, }, }) } // vcardDo ejecuta una llamada a la API Admin VCard y devuelve el body como json.RawMessage. func vcardDo(method, endpoint string, cfg *models.VcardApiConfig) ([]byte, int, error) { return vcardDoWithBody(method, endpoint, nil, "", cfg) } // vcardDoWithBody ejecuta una llamada a la API Admin VCard con body opcional. func vcardDoWithBody(method, endpoint string, reqBody io.Reader, contentType string, cfg *models.VcardApiConfig) ([]byte, int, error) { url := fmt.Sprintf("%s%s", cfg.BaseURL, endpoint) client := &http.Client{Timeout: 15 * time.Second} req, err := http.NewRequest(method, url, reqBody) if err != nil { return nil, 0, err } req.Header.Set("Authorization", "Bearer "+cfg.BearerToken) 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, 512*1024)) return body, resp.StatusCode, nil } // proxyVcard extrae la config, llama al endpoint y devuelve el resultado al frontend. func proxyVcard(c *fiber.Ctx, method, endpoint string) error { cfg, err := models.GetVcardApiConfig() if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Configura primero la API VCard"}) } if !cfg.Activo { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La integración está inactiva"}) } qs := string(c.Request().URI().QueryString()) ep := endpoint if qs != "" { ep = endpoint + "?" + qs } body, status, err := vcardDo(method, ep, cfg) if err != nil { return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()}) } var result json.RawMessage if err := json.Unmarshal(body, &result); err != nil { result = json.RawMessage(fmt.Sprintf(`{"raw": %q}`, string(body))) } c.Status(status) return c.JSON(result) } // proxyVcardMutate reenvía el body del request original a la API VCard. func proxyVcardMutate(c *fiber.Ctx, method, endpoint string) error { cfg, err := models.GetVcardApiConfig() if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Configura primero la API VCard"}) } if !cfg.Activo { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La integración está inactiva"}) } 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 := vcardDoWithBody(method, endpoint, bodyReader, ct, cfg) if err != nil { return c.Status(fiber.StatusBadGateway).JSON(fiber.Map{"error": err.Error()}) } var result json.RawMessage if err := json.Unmarshal(body, &result); err != nil { result = json.RawMessage(fmt.Sprintf(`{"raw": %q}`, string(body))) } c.Status(status) return c.JSON(result) } // ─── Proxy endpoints ────────────────────────────────────────────────────────── func VcardApiUsuarios(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/usuarios") } func VcardApiUsuario(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("id")) } func VcardApiVcards(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/vcards") } func VcardApiVcard(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/vcards/"+c.Params("id")) } func VcardApiVcardsByUsuario(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/vcards") } func VcardApiPlanes(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/planes") } func VcardApiMembresia(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("id")+"/membresia") } func VcardApiPagos(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/pagos") } func VcardApiPagosByUsuario(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/pagos") } func VcardApiTransacciones(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/transacciones") } func VcardApiTransaccionesByUsuario(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/transacciones") } func VcardApiLogs(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/logs") } func VcardApiLogsByUsuario(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/logs") } func VcardApiMiniwebs(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/miniwebs") } func VcardApiMiniweb(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/miniwebs/"+c.Params("id")) } func VcardApiMiniwebsByUsuario(c *fiber.Ctx) error { return proxyVcard(c, http.MethodGet, "/api/admin/usuarios/"+c.Params("userId")+"/miniwebs") } // ─── Mutación endpoints ─────────────────────────────────────────────────────── func VcardApiUsuarioUpdate(c *fiber.Ctx) error { return proxyVcardMutate(c, http.MethodPut, "/api/admin/usuarios/"+c.Params("id")) } func VcardApiUsuarioActivar(c *fiber.Ctx) error { return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/activar") } func VcardApiUsuarioDesactivar(c *fiber.Ctx) error { return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/desactivar") } func VcardApiVcardUpdate(c *fiber.Ctx) error { return proxyVcardMutate(c, http.MethodPut, "/api/admin/vcards/"+c.Params("id")) } func VcardApiActivarMembresia(c *fiber.Ctx) error { return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/activar-membresia") } func VcardApiDesactivarMembresia(c *fiber.Ctx) error { return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/desactivar-membresia") } func VcardApiCambiarPlan(c *fiber.Ctx) error { return proxyVcardMutate(c, http.MethodPost, "/api/admin/usuarios/"+c.Params("id")+"/cambiar-plan") }