feat: add VCard API integration with configuration and proxy endpoints

- Implemented VCard API controller with methods to manage configuration (save, get).
- Added proxy functions to handle API requests for users, vcards, memberships, payments, transactions, logs, and miniwebs.
- Included error handling and response formatting for API interactions.
This commit is contained in:
Lizandro Guarnizo
2026-05-19 20:08:50 -05:00
parent 749cf7ef2f
commit 24e1cf1439
8 changed files with 1303 additions and 3 deletions
+248
View File
@@ -0,0 +1,248 @@
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")
}
+33 -1
View File
@@ -21,7 +21,7 @@ func UserRoutes(app fiber.Router) {
// Rutas de la aplicación
// web me redireccione a /
// web me redireccione a /
protected.Get("/web", func(c *fiber.Ctx) error { return c.Redirect("/", http.StatusSeeOther) })
app.Get("/web", func(c *fiber.Ctx) error { return c.Redirect("/", http.StatusSeeOther) })
protected.Get("/", controllers.App)
@@ -215,6 +215,38 @@ func UserRoutes(app fiber.Router) {
protected.Get("/saas-api/logs", middlewares.MenuMiddleware, controllers.SaasDispatchLogIndex)
protected.Get("/loadsaasdispatchlogs", controllers.GetSaasDispatchLogs)
// ─── VCard API (integración Admin Laravel) ────────────────────────────────
protected.Get("/vcard-api", middlewares.MenuMiddleware, controllers.VcardApiIndex)
protected.Get("/vcard-api/config", controllers.VcardApiGetConfig)
protected.Post("/vcard-api/config", controllers.VcardApiSaveConfig)
// Usuarios (GET)
protected.Get("/vcard-api/usuarios", controllers.VcardApiUsuarios)
protected.Get("/vcard-api/usuarios/:id/membresia", controllers.VcardApiMembresia)
protected.Get("/vcard-api/usuarios/:userId/vcards", controllers.VcardApiVcardsByUsuario)
protected.Get("/vcard-api/usuarios/:userId/pagos", controllers.VcardApiPagosByUsuario)
protected.Get("/vcard-api/usuarios/:userId/transacciones", controllers.VcardApiTransaccionesByUsuario)
protected.Get("/vcard-api/usuarios/:userId/logs", controllers.VcardApiLogsByUsuario)
protected.Get("/vcard-api/usuarios/:userId/miniwebs", controllers.VcardApiMiniwebsByUsuario)
protected.Get("/vcard-api/usuarios/:id", controllers.VcardApiUsuario)
// Usuarios (mutaciones)
protected.Put("/vcard-api/usuarios/:id", controllers.VcardApiUsuarioUpdate)
protected.Post("/vcard-api/usuarios/:id/activar", controllers.VcardApiUsuarioActivar)
protected.Post("/vcard-api/usuarios/:id/desactivar", controllers.VcardApiUsuarioDesactivar)
protected.Post("/vcard-api/usuarios/:id/activar-membresia", controllers.VcardApiActivarMembresia)
protected.Post("/vcard-api/usuarios/:id/desactivar-membresia", controllers.VcardApiDesactivarMembresia)
protected.Post("/vcard-api/usuarios/:id/cambiar-plan", controllers.VcardApiCambiarPlan)
// VCards
protected.Get("/vcard-api/vcards", controllers.VcardApiVcards)
protected.Get("/vcard-api/vcards/:id", controllers.VcardApiVcard)
protected.Put("/vcard-api/vcards/:id", controllers.VcardApiVcardUpdate)
// Resto (solo GET)
protected.Get("/vcard-api/planes", controllers.VcardApiPlanes)
protected.Get("/vcard-api/pagos", controllers.VcardApiPagos)
protected.Get("/vcard-api/transacciones", controllers.VcardApiTransacciones)
protected.Get("/vcard-api/logs", controllers.VcardApiLogs)
protected.Get("/vcard-api/miniwebs", controllers.VcardApiMiniwebs)
protected.Get("/vcard-api/miniwebs/:id", controllers.VcardApiMiniweb)
// ─── Telegram ─────────────────────────────────────────────────────────────
protected.Get("/telegram", middlewares.MenuMiddleware, controllers.TelegramIndex)
protected.Get("/loadtelegram", controllers.GetTelegramConfigs)